npm install --save-dev vitest @testing-library/react @testing-library/jest-dom jsdom @types/testing-library__jest-dom
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom', // 브라우저 환경을 시뮬레이션
setupFiles: './setupTests.ts', // 테스트 설정 파일
},
});
import '@testing-library/jest-dom';
"scripts": {
...
"test": "vitest"
}
{...
"compilerOptions": {"types": ["vitest/client", "jest", "@testing-library/jest-dom"]
}
루트 경로나 적당한 폴더에 __tests__ 이름의 폴더를 만들고 (컴포넌트 이름).test.tsx 형태로
파일을 생성한 후 테스트 코드를 작성한다.
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders the app component', () => {
render(<App />);
const headingElement = screen.getByText(/hello vite/i);
expect(headingElement).toBeInTheDocument();
});

1. Signup.tsx
const schema = yup.object().shape({
userId: yup
.string()
.matches(/^(?=.*[a-z])(?=.*\d)[a-z\d]{6,}$/, "아이디는 최소 6자 이상, 영소문자와 숫자를 포함해야 합니다.")
.required("아이디를 입력하세요."),
...
})
export default function Signup() {
const {
register,
handleSubmit,
control,
formState: { errors, isValid },
} = useForm<LoginInputs>({mode : "onChange", resolver : yupResolver(schema)});
const navigate = useNavigate();
...
return (
<Container>
<SignupForm onSubmit={handleSubmit(fetchLoginInfo)}>
<Title>회원가입</Title>
<Flex style={{marginTop : '20px'}}>
<Label htmlFor="userId">아이디</Label>
<Input
id="userId"
type="text"
{...register("userId")}
border={errors.userId && 'red'}
placeholder="아이디를 입력하세요"
/>
</Flex>
...
)
2. /__test__/Signup.test.tsx
테스트 통과 조건 : 아이디는 최소 6자 이상, 영소문자와 숫자를 포함
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import Signup from "../pages/Signup";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import Schedule from "../pages/Schedules";
test('아이디는 최소 6자이상, 영소문자와 숫자를 포함해야 한다.', async () => {
render(
<MemoryRouter initialEntries={["/signup"]}>
<Routes>
<Route path="/signup" element={<Signup />} />
<Route path="/schedule" element={<Schedule />} /> {/* 회원가입 후 이동할 페이지 */}
</Routes>
</MemoryRouter>);
const errorMessageValid = "아이디는 최소 6자 이상, 영소문자와 숫자를 포함해야 합니다."
const errorMessageNoInput ="아이디를 입력하세요."
const idInputElement = screen.getByPlaceholderText("아이디를 입력하세요");
expect(idInputElement).toBeInTheDocument();
// 입력값 변경 (fireEvent 사용)
fireEvent.change(idInputElement, { target: { value: 'ab12'} });
await waitFor(() => {
expect(screen.getByText(errorMessageValid)).toBeInTheDocument();
})
fireEvent.change(idInputElement, { target: { value: ''} });
await waitFor(() => {
expect(screen.getByText(errorMessageNoInput)).toBeInTheDocument();
})
await waitFor(() => {
expect(screen.queryByText(errorMessageValid)).not.toBeInTheDocument();
}, { timeout: 500 });
});
실제 코드에서 useNavigater() 훅을 사용해서 컴포넌트를 구현한 부분이 있어서 MemoryRouter 사용.
테스트 코드에서 브라우저 환경에서 BrowserRouter를 직접 사용하는 대신, MemoryRouter를 활용한다. MemoryRouter는 원하는 초기 경로를 설정하여 특정 페이지를 테스트 할 수 있다.
getBy* : 일치하는 요소가 없으면 오류를 발생
queryBy* : 일치하는 요소가 없으면 null을 반환
1.screen.getByPlaceholderText() : 해당 placeholder 텍스트가 존재하는 요소 가져오기
2.screen.getByText('abc') : abc의 문자열이 존재하는 요소를 가져오기
3.screen.getAllByTest('abc') : abc의 문자열이 존재하는 요소를 전부가져오기
4.screen.getByRole('cell', {name : '가나다'})
: <td>가나다</td> 요소가 있는지 확인 -> 여기서 cell은 <td> 태그를 의미.
| 실제 HTML 요소 | getByRole() 에서의 역할 |
|---|---|
<td> | cell |
<th> | columnheader 또는 rowheader (테이블 컨텍스트) |
<section> | region (하지만 aria-labelledby가 필요함) |
<article> | article |
<header> | banner (하지만 의 직계 자식이어야 함) |
<footer> | contentinfo (하지만 의 직계 자식이어야 함) |
<h1> ~ <h6> | heading (레벨은 level 속성으로 지정됨) |
<ul> | list |
<ol> | list |
<li> | listitem |
<a> (with href) | link |
<button> | button |
<input type="text"> | textbox |
<input type="number"> | spinbutton |
<input type="checkbox"> | checkbox |
<input type="radio"> | radio |
<input type="range"> | slider |
<input type="search"> | searchbox |
<textarea> | textbox |
<select> | combobox |
<option> | option (부모가 <select>일 때) |
<dialog>} | dialog |
<fieldset> | group |
<label> | 없음 (보통 form 요소의 aria-labelledby로 활용됨) |
5.screen.getByTestId('spinner') : data-testId 속성이 "spinner"인 값 가져오기
<img src={SpinnerSvg} data-testId="spinner"/>
__tests__ 전체 테스트) npx vitest __tests__
또는
npm run test __tests__ : (package.json에 "test": "vitest"가 설정되어 있어야 함)
npx vitest __tests__/Signup.test.tsx