[React.js]Testing-library와 Jest 로 사용자 동작 흐름 시뮬레이션

유선향·2025년 6월 12일

<React>

목록 보기
1/5

테스트에 관심을 가지게 된 이유...

이전에 팀프로젝트를 몇차 진행해보면서, 리팩토링 같은 큰 구조 변화에 갑자기 되던 기능이 안되는 경험을 했었다. 이를 사전에 방지하기 위해 테스트 라이브러리와 github Action으로 PR 생성시에 테스트가 동작하게끔 하려 한다.


Testing Library를 사용한 이유

React Docs -act

  • React 의 act API로 테스트를 진행해 볼수 있지만, 위와같이 React 문서 내에서도 보일러플레이트를 피하기 위해 React Testing Library 와 같은 라이브러리 사용을 권장하고 있다.

  • 또한 act API 를 사용하기 위해 설정해야하는 global.IS_REACT_ACT_ENVIRONMENT=true 가 React Testing Library 에는 이미 설정되어 있다!

  • 나는 사용자의 실제 동작 흐름을 시뮬레이션할 수 있는 도구가 필요했기에, DOM 중심의 접근 방식을 제공하여 실제 사용자와 가장 유사한 방식으로 컴포넌트를 테스트할 수 있는 @testing-library/react 를 선택했다.
    그리고 jest와의 연동이 자연스럽고, mock 기능을 통해 외부 API 호출을 제어하거나 네트워크 의존성을 없애는 테스트 환경 구성이 가능해서 안정적인 테스트 자동화가 가능하다는 점도 장점이라고 생각했다.

React Testing Library
Jest docs


axios 모킹하기

사용자의 실제 동작 흐름을 시뮬레이션 하기 위해선, mock 기능으로 axios.create()로 인스턴스와 외부 api 호출을 흉내내고, 반환값또한 mocking 하여야 한다 사실 이부분이 제일 어려웠다 😢

//jest.config.js

export default {
  preset: 'ts-jest/presets/js-with-ts',
  setupFiles: ['<rootDir>/jest.setup.ts'], //setup 파일과 env setup 파일을 구분한다.
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup-env.ts'], //setup 파일과 env setup 파일을 구분한다.
  moduleFileExtensions: ['ts', 'tsx', 'js'],
  transform: {
    '^.+\\.tsx?$': ['ts-jest', { tsconfig: './tsconfig.json' }],
  },
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.json',
    },
  },
};

테스트 환경에서 제대로 mock되지 않아서 undefined인 상태인데, 거기 에.interceptors.request.use(...)를 호출하려 하면 아래와 같은 문제가 발생했다.

● Test suite failed to run

    TypeError: Cannot read properties of undefined (reading 'interceptors')
//interceptors를 undefined로 취급된다.
    > 12 | axiosInstance.interceptors.request.use((config) => {
         |               ^
...
      at Object.<anonymous> (src/lib/instance.ts:12:15)

따라서 아래와 같이 axios의 인스터스, 인터셉터, 반환값을 모두 흉내내야 한다.

//login-flow.test.ts
jest.resetModules(); 
//모듈 레지스트리(필수 모듈의 캐시)를 재설정, 테스트 간에 로컬 상태가 충돌할 수 있는 모듈을 격리하는 데 사용한다.
jest.mock('axios'); //axios를 모킹한다.

import axios, { AxiosInstance } from 'axios';
const mockedAxios = axios as jest.Mocked<typeof axios>; //타입스크립트 사용시

const mockAxiosInstance = {
  interceptors: { //인터셉터 모킹
    request: {
      use: jest.fn(),//사용되지 않는 모의 함수를 반환 리퀘스트 함수를 흉내내기 위해 작성되어있다.
    },
    response: {
      use: jest.fn(),
    },
  },
  post: jest.fn(),
  get: jest.fn(),
  ...
} as unknown as jest.Mocked<AxiosInstance>;
mockedAxios.create.mockReturnValue(mockAxiosInstance);

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import LoginPage from '../../pages/login';
import { AUTH_BUTTON } from '../../constants/button';

test('사용자는 이메일과 비밀번호로 로그인할 수 있다', async () => {
  mockAxiosInstance.post.mockResolvedValue({
    data: { accessToken: 'fake-token' },
  });

  render(
    <MemoryRouter>
      <LoginPage />
    </MemoryRouter>
  );

  userEvent.type(screen.getByPlaceholderText('이메일을 입력해주세요'), 'test00@test.com');
  userEvent.type(screen.getByPlaceholderText('비밀번호를 입력해주세요'), '1234abcd');

  userEvent.click(screen.getByRole('button', { name: AUTH_BUTTON.login }));

  await waitFor(() => {
    expect(localStorage.setItem('accessToken', 'fake-token'));
  });
});

import.meta 오류

Jest는 모든 import 된 파일을 미리 파싱 하는데, import.meta.env는 Node 환경이나 Jest에서 기본 지원하지 않아서 아래와 같은 에러가 터진다.

...
    exports.BASE_URL = typeof import.meta !== 'undefined' && import.meta.env
                                     ^^^^
    SyntaxError: Cannot use 'import.meta' outside a module    
...
export const BASE_URL = import.meta.env.VITE_PUBLIC_API_URL;

const axiosInstance = axios.create({
  baseURL: BASE_URL,
  ...

따라서 env 또한 BASE_URL 또한 mock 해주어야 한다.


//signup-flow.test.tsx
jest.mock('../../lib/instance.ts', () => ({
  BASE_URL: 'https://mock-api.test',
}));

드디어 테스트 성공!!

PR 생성시에도 확인 할수 있고,

git workflow파일에 특정 브랜치로 pr 생성 액션시에 테스트를 동작시키게 할수도 있다.
물론 editor에서도 run test명령어, vscode의 jest 관련 확장 프로그램에서도 쉽게 테스트를 실행해볼수 있다.

//.github/workflows/test.yml
name: Run Tests

on:
  pull_request:
    branches: [main, dev] //main,dev 브랜치에 PR이 생성될때 

jobs:
  test:
    runs-on: ubuntu-latest // git이나 vercel에서는 ubuntu 환경에서 테스트가 실행된다.

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Install Dependencies
        run: npm ci

      - name: Run User Flow Tests Only 
        run: npx jest src/__tests__/flows //위에서 살펴본 flows 폴더내의 test 파일이 실행된다.


이전에 팀프로젝트를 몇차 진행해보면서, 리팩토링 같은 큰변화때에 빌드시에 오류는 없었지만 갑자기 되던 기능이 안되는 경험을 했었다. 또한 프로젝트 규모가 커질수록 코드구조의 변화로 인해 기능이 갑자기 안되는 문제를 사전에 방지하는 역할이 될것 같아서 꼭 시도해보고 싶었기에, 아쉽지만 개인 프로젝트에서 셋업과 동작 원리, 환경에 대해 조금이나마 공부해 보게 되었다.

0개의 댓글