[React / Vite] 테스트 환경 구축 및 테스트 실행

Jayden ·2025년 2월 19일

1. 환경 세팅

1) 패키지 설치

npm install --save-dev vitest @testing-library/react @testing-library/jest-dom jsdom @types/testing-library__jest-dom

2) Vite 설정 파일 수정(vite.config.ts)

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',  // 브라우저 환경을 시뮬레이션
    setupFiles: './setupTests.ts',  // 테스트 설정 파일
  },
});

3) 테스트 설정 파일 생성(setupTests.ts)

import '@testing-library/jest-dom';

4) package.json 파일 수정

"scripts": {
  ...
  "test": "vitest"
}

5) tsconfig.json 파일 수정

{...
  "compilerOptions": {"types": ["vitest/client", "jest", "@testing-library/jest-dom"]
}

5) 테스트 코드 작성

루트 경로나 적당한 폴더에 __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();
});

2. 테스트 코드 작성

1) 회원가입 페이지 테스트

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자 이상, 영소문자와 숫자를 포함

1. '' 입력 => "아이디를 입력하세요." 에러 메시지 표시

2. 'ab12'(6자 미만) 입력 => "아이디는 최소 6자 이상, 영소문자와 숫자를 포함해야 합니다." 에러 메시지 표시

3. 'abcd12345' 유효성 만족 => 에러 메시지가 존재하지 않아야함.

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는 원하는 초기 경로를 설정하여 특정 페이지를 테스트 할 수 있다.

DOM 요소를 선택하는 다양한 방법

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> 태그를 의미.

  • getByRole()과 실제 DOM 요소 비교
실제 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"/>

3. 테스트 실행

1) 특정 폴더의 테스트 실행 (__tests__ 전체 테스트)

npx vitest __tests__
또는
npm run test __tests__ : (package.json에 "test": "vitest"가 설정되어 있어야 함)

2) 특정 파일만 실행

npx vitest __tests__/Signup.test.tsx

profile
프론트엔드 개발자

0개의 댓글