Jest 설치 및 환경 설정
npm install --save-dev jest @types/jest ts-jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom
터미널에 위와 같은 명령어를 입력해 Jest를 설치해준다.
정확히 어떤 것들인지 잘 몰라서 하나하나 서치해서 정리해봤다.
먼저 강의에서는 TS를 사용하기 때문에 Jest의 타입을 정의해줄 @types/jest와 ts-jest도 설치해줬다.
jest-environment-jsdom은 Jest 테스트 환경을 JSDOM으로 설정하는 패키지다.
JSDOM은 Node.js 환경에서 브라우저 환경을 시뮬레이션 해서 DOM API를 사용할 수 있게 해준다.
React처럼 브라우저에 의존하는 코드를 테스트 할 때 유용하다.
@testing-library/react는 React 컴포넌트를 테스트 하기 위한 테스팅 라이브러리의 React 버전이다.
@testing-library/dom도 비슷한데 DOM 노드에 대한 테스트 유틸리티를 제공하는 패키지로 DOM을 직접 다루는 테스트를 작성할 때 유용하며, React의 테스팅 라이브러리에 기반이 되는 패키지다.
마지막으로 @testing-library/jest-dom은 Jest matcher를 확장해 DOM 노드 상태를 더 직관적으로 테스트 할 수 있게 해주는 패키지다. 여기서 matcher는 테스트 라이브러리에서 테스트의 기댓값(우리가 기대하는 상태)를 표현하기 위해 사용되는 함수로, 특정 값이 기대하는 조건을 만족하는지 확인하는데 사용된다. 결론적으로 제대로 동작하는지 확인하는 함수를 확장해서 더 직관적으로 DOM을 쉽게 테스트 할 수 있도록 해주는 것이다. (ex. .toBeIntheDocument() -> 해당 요소가 문서에 존재하는지 확인, .toHaveTextContent() -> 요소가 특정 테스트를 포함하는지 확인)
Jest 환경 설정
환경 설정을 하는 방법은 2가지가 있다.
1) jest.config.cjs 생성하기
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jest-environment-jsdom',
}
preset 옵션은 Jest의 기본 설정을 지정하는 것으로, 현재 우리는 TS를 사용하기 때문에 ts-jest로 지정한다.
testEnvironment는 테스트가 실행될 환경을 지정하는 것으로, 아까 설치한 jest-environment-jsdom으로 지정한다.
2) package.json에 작성하기
// package.json
"test": "jest --watchAll",
...
"jest": {
"preset": "ts-jest",
"testEnvironment": "jest-environment-jsdom",
},
위에서 적은 내용을 package.json에 적어주면 된다.
다음으로 1번으로 하든 2번으로 하든 script에 명령어 jest --watchAll를 적어준다.
일반적으로 테스트 하려는 컴포넌트와 똑같은 이름을 쓰되 [똑같은이름.spec.tsx]처럼 작성해 준다.
똑같은 이름끼리 묶어도 되지만 __test__ 디렉토리에 테스트 코드들만 모아서 관리하기로 했다.
(이 부분은 속한 프로젝트 또는 팀의 컨벤션에 따라 상이할 수 있다.)
사전 세팅
이번 예제는 회원 가입 기능을 통해 유닛 테스트를 진행한다.
1. 이메일, 비밀번호, 비밀번호 확인 인풋 3가지가 있고 회원 가입 버튼이 있는 페이지가 있을 때
2. 이메일과 비밀번호, 비밀번호 확인을 입력한다.
3. 비밀번호와 비밀번호 확인의 값이 다른 경우에는 에러 메세지를,
4. 그렇지 않을 때는 회원가입 버튼이 활성화 되어 로그인으로 이동한다.
5. 로그인 페이지에서 로그인을 할 때 정상적인 정보라면 /으로 이동한다.
우선 비밀번호와 비밀번호 확인의 값이 다를 때 어떻게 되는지를 테스트 해보자!
먼저 given, when, then 3가지 컨디션을 확인해야 한다.
given은 회원가입 페이지가 그려진 것,
when은 비밀번호와 비밀번호 확인 값이 일치하지 않은 것,
then은 에러 메세지가 발생하는 것이 되겠다.
describe('회원가입 테스트', () => {
test('비밀번호와 비밀번호 확인 값이 일치하지 않으면 에러 메세지가 표시된다.', () => {
// given - 회원가입 페이지가 그려짐
// when - 비밀번호와 비밀번호 확인 값이 일치하지 않음
// then - 에러 메세지가 표시됨
});
});
given부터 채워보면 회원가입 페이지를 렌더 해야 하는데, 이 때 render(<SignupPage />)를 작성하면 사용할 수 없다는 에러가 발생한다. (useNavigate는 <Router /> 안에서만 쓸 수 있다는 에러)
이유는 SignupPage에서 react-router-dom과 react-query를 사용하고 있기 때문으로, 실제 페이지를 라우팅 할 때처럼 랩핑해주는 작업이 필요하다.
const queryClient = new QueryClient({
defaultOptions: {},
});
const routes = [
{
path: '/signup',
element: <SignupPage />,
},
];
const router = createMemoryRouter(routes, {
initialEntries: ['/signup'],
initialIndex: 0,
});
render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
이렇게 코드를 작성해주자.
createMemoryRouter는 처음 써봐서 공식 문서를 찾아봤다.
Instead of using the browser's history, a memory router manages its own history stack in memory. It's primarily useful for testing and component development tools like Storybook, but can also be used for running React Router in any non-browser environment.
메모리 라우터는 브라우저의 히스토리를 사용하는 대신 메모리에서 자체 히스토리 스택을 관리한다.
주로 Storybook과 같은 테스트 및 컴포넌트 개발 도구에 유용하지만 브라우저가 아닌 환경에서도 React Router를 실행하는데 사용할 수 있다.
routes 배열을 라우터의 설정으로 사용한다.
또 initialEntries는 초기 경로를 설정하는데, 여기서는 ['/signup']로 설정되어 있어 테스트가 시작될 때 /signup 경로로 이동하게 된다.
마찬가지로 initialIndex는 초기 경로의 인덱스를 설정하며, 여기서는 0으로 설정되어 있어 initialEntries 배열의 첫 번째 경로로 이동합니다.
그리고 QueryClientProvider로도 감싸준다.
(defaultOptions는 queries와 mutations의 기본 옵션을 설정하는데 사용되는데 현재 빈 객체이므로 TanStack Query의 기본 설정을 따르게 된다.)
이로써 일단 given은 작성됐다.
실패 케이스 작성
이제 when과 then을 작성해주면 된다.
when은 비밀번호와 비밀번호 확인 값이 같지 않을 때를 말한다.
SignupPage 컴포넌트의 비밀번호와 비밀번호 확인 필드를 선택해서 가지고 오자.
const passwordInput = screen.getByLabelText("비밀번호");
const confirmPasswordInput = screen.getByLabelText("비밀번호 확인");
screen.getLabelText는 @testing-library/react에서 제공하는 쿼리 함수 중 하나로, 접근성을 기반으로 HTML 요소를 선택할 수 있다. 주로 label 요소와 연결된 입력 필드를 찾는데 사용된다. 이렇게 비밀번호와 비밀번호 확인 필드를 가지고 왔다!
다음으로 비밀번호와 비밀번호 확인 값이 변경되는 이벤트를 시뮬레이션 해보자.
fireEvent.change(passwordInput, { target: { value: "password" } });
fireEvent.change(confirmPasswordInput, {
target: { value: "wrongPassword" },
});
fireEvent도 위와 마찬가지로 @testing-library/react에서 제공하는 유틸리티로 사용자 이벤트를 시뮬레이션하는데 사용된다. 이 유틸리티를 사용해서 클릭, 입력, 폼 제출 같은 유저 상호 작용을 테스트 할 수 있다.
우리는 지금 해당 필드의 값이 변경되는 이벤트를 시뮬레이션 하고자 하기 때문에 fireEvent.change로 작성해준다.
즉 fireEvent.change로 첫 번째 인자를 선택하고 타겟의 value를 "password", "wrongPassword"로 바꾸면 된다.
이렇게 일치하지 않는 상황을 시뮬레이션 했고, 이 경우에 에러 메세지가 뜨는 then을 작성해보자.
test("비밀번호와 비밀번호 확인 값이 일치하지 않으면 에러메세지가 표시된다", async () => {
// 이 안에 given과 when이 있다...
// then
const errorMessage = await screen.findByTestId("error-message");
expect(errorMessage).toBeInTheDocument();
screen.findByTestId도 @testing-library/react에서 제공하는 메서드로, 주어진 data-testid 속성을 가진 요소를 비동기적으로 찾는다. 이 메서드는 요소를 찾을 때까지 기다렸다가 요소를 반환하고 지정된 시간 내 요소를 찾지 못하면 에러를 던진다. 에러 메세지를 찾을 때까지 기다려야 하기 때문에 await 키워드가 필요하다.
errorMessage를 찾고 toBeInTheDocument를 사용해 해당 요소가 문서에 있는지 확인한다.
완성된 회원가입 실패 테스트 케이스는 아래와 같다.
// Signup.spec.tsx
test("비밀번호와 비밀번호 확인 값이 일치하지 않으면 에러메세지가 표시된다", async () => {
// given - 회원가입 페이지가 그려짐
const routes = [
{
path: "/signup",
element: <SignupPage />,
},
];
const router = createMemoryRouter(routes, {
initialEntries: ["/signup"],
initialIndex: 0,
});
render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
// when - 비밀번호와 비밀번호 확인 값이 일치하지 않음
const passwordInput = screen.getByLabelText("비밀번호");
const confirmPasswordInput = screen.getByLabelText("비밀번호 확인");
fireEvent.change(passwordInput, { target: { value: "password" } });
fireEvent.change(confirmPasswordInput, {
target: { value: "wrongPassword" },
});
// then - 에러메세지가 표시됨
const errorMessage = await screen.findByTestId("error-message");
expect(errorMessage).toBeInTheDocument();
});
참고 강의
'⚛️ React > 📜 Test Code' 카테고리의 다른 글
[Test Code] Cypress로 본격적인 테스트 코드 작성하기(fixture, recoil mocking) (0) | 2024.07.02 |
---|---|
[Test Code] Cypress로 테스트 하기 (0) | 2024.07.02 |
[Test Code] react-query 공식 문서를 따라 테스트 코드를 하면 안되는 이유 + nock으로 mocking 하기 (0) | 2024.06.26 |
[Test Code] beforeEach()를 활용한 Jest 성공 케이스 작성 (0) | 2024.06.19 |
[Test Code] About Test Code + Jest (2) | 2024.06.19 |