[React] Test 환경 구축과 테스트 예제
2021. 12. 16. 11:02ㆍ공부내용 공유하기
React Testing
💡 Jest, ReactTestingLibrary로 Testing 환경을 구축했던 경험을 공유한다.
도입 취지
TDD(Test Driven Development)가 아니라도 테스트를 통해 비즈니스의 핵심 로직 검증만이라도 가능하다면, 코드의 리팩토링이나 기능의 추가, 수정 시에 더욱 안정성을 가질 수 있다고 생각되었음.
공통적인 테스트 코드 환경 구축 및 컨벤션이 있다면 최소한의 노력으로 최대한의 효과를 볼 수 있을거라 기대됨.
환경 구축
설치
💡 CRA기준으로는 이미 jest가 설치되어 있고, 다른 툴체인에서는 jest를 설치해준다.
그 외 설치 필요한 항목
- yarn add
@testing-library/react
@types/jest
ts-jest
@testing-library/user-event
@testing-library/jest-dom
test 환경을 커스텀하기 위해 eject
를 권장한다.
config 세팅
- eject 후 package.json에 jest 항목을 찾을 수 있다.
- jest 항목을 삭제한다.
- root 경로에 jest.config.js를 생성해준다.
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { compilerOptions } = require("./tsconfig");
module.exports = {
preset: "ts-jest",
globals: {
__DEV__: true,
},
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"],
setupFiles: ["react-app-polyfill/jsdom"],
testMatch: [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}",
],
testEnvironment: "jsdom",
testRunner: "<rootDir>/node_modules/jest-circus/runner.js",
transform: {
"^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "<rootDir>/config/jest/babelTransform.js",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)":
"<rootDir>/config/jest/fileTransform.js",
},
transformIgnorePatterns: ["^.+\\.module\\.(css|sass|scss)$"],
moduleFileExtensions: [
"web.js",
"js",
"web.ts",
"ts",
"web.tsx",
"tsx",
"json",
"web.jsx",
"jsx",
"node",
],
watchPlugins: [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname",
],
resetMocks: true,
roots: ["<rootDir>"],
modulePaths: ["<rootDir>"],
moduleDirectories: [".", "src", "node_modules"],
testTimeout: 10000,
moduleNameMapper: {
"^react-native$": "react-native-web",
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
...pathsToModuleNameMapper(compilerOptions.paths),
},
};
pathsToModuleNameMapper 메소드는 tsconfig.json에 있는 절대 경로 세팅을 jest에서도 인식이 가능하게 처리하는 메소드이다. tsconfig.json에서 절대경로 세팅을 하지 않는 경우에는 제외해도 된다.
테스트
src/pages/login.tsx
import React, { useState } from "react";
export interface Props {}
const Login: React.FC<Props> = (props: Props) => {
const [id, setId] = useState("");
const [isOk, setIsOk] = useState(false);
const handleSubmit = async () => {
setTimeout(() => {
setIsOk(true);
}, 500);
};
return (
<div>
{isOk && <h1>통과되었습니다.</h1>}
<input
data-testid={"loginId"}
name={"id"}
value={id}
onChange={(e) => setId(e.target.value)}
/>
<button onClick={handleSubmit}>제출하기</button>
</div>
);
};
export default Login;
src/test/login/index.test.tsx
import { fireEvent, render, screen } from "@testing-library/react";
import Login from "../../pages/login";
import React from "react";
describe("로그인 화면 테스트", () => {
it("아이디 입력 후 제출 테스트", async () => {
// 로그인 컴포넌트 렌더링
render(<Login />);
// 렌더링 된 컴포넌트에서 select 메소드 사용
// test id로 element 가져오기
const idInput = screen.getByTestId("loginId") as HTMLInputElement;
// change event로 input 값 변경
fireEvent.change(idInput, { target: { value: "testid1234" } });
expect(idInput.value).toEqual("testid1234");
// 제출버튼 찾아서 제출
const submitButton = screen.getByText("제출하기");
fireEvent.click(submitButton);
screen.logTestingPlaygroundURL();
// 클릭 이벤트에 따른 비동기 작업 발생 -> 기다렸다가 통과 확인
await screen.findByText("통과되었습니다.");
});
});
이외 고려 사항들 (Other Considerations)
- react-native 환경에서 테스트를 하기 위해서는 추가적인 환경 세팅이 필요하다.
- react-dom 환경에서의 테스트는 screen.getByText 등으로 현재 렌더링 된 화면에 직접 접근이 가능해서 편리하다.
- Redux, Styled-Components 등의 Provider가 필요한 경우에는 추가적으로 render 쪽에서 Provider를 넘겨주던지, HOC를 만들어서 작업해야 한다.