[React] Jest 프로젝트에 Vitest 도입하기 - View 테스트 분리 전략

2026-03-09 hit count image

기존 Jest로 작성된 React 프로젝트에 Vitest를 추가하여 View 테스트와 로직 테스트를 분리하는 방법을 소개합니다. 모노레포 환경에서의 설정 방법과 CI 통합까지 다룹니다.

react

개요

기존에 Jest로 모든 테스트를 작성하고 있던 React 프로젝트에 Vitest를 도입한 경험을 공유합니다. 모든 테스트를 한 번에 마이그레이션하는 것이 아니라, View(컴포넌트) 테스트는 Vitest로, Actions/로직 테스트는 Jest로 분리하는 전략을 취했습니다.

왜 Vitest를 추가하는가

Jest는 React 생태계에서 오랫동안 표준 테스트 프레임워크로 사용되어 왔지만, 몇 가지 한계가 있습니다.

  • Vite와의 이중 설정: 프로덕트 코드는 Vite로 빌드하면서 테스트는 Jest로 실행하면, ts-jest, jest-svg-transformer 등 별도의 변환 설정이 필요합니다.
  • import.meta.env 지원: Vite 프로젝트에서 환경 변수를 import.meta.env로 접근하는데, Jest에서는 ts-jest-mock-import-meta 같은 별도 패키지가 필요합니다.
  • 스냅샷 테스트의 유지보수 비용: Jest 스냅샷 테스트는 HTML 구조 변경만으로도 깨지기 쉽고, 대규모 스냅샷 파일은 리뷰가 어렵습니다.

Vitest는 Vite 네이티브 테스트 프레임워크로, 이러한 문제를 근본적으로 해결합니다.

  • Vite의 모듈 해석과 변환 파이프라인을 그대로 사용하므로 별도의 변환 설정이 불필요합니다.
  • import.meta.env를 네이티브로 지원합니다.
  • happy-dom을 사용하면 jsdom보다 빠른 DOM 환경을 제공합니다.

전략: 테스트 러너 분리

한 번에 모든 테스트를 마이그레이션하면 리스크가 크기 때문에, 테스트 대상에 따라 러너를 분리했습니다.

테스트 대상러너이유
View(컴포넌트) 테스트 (view/ 하위)VitestVite 환경과 일치, CSS/SVG 처리가 자연스러움
Actions/Controller/Utils/Selectors 테스트Jest기존 테스트 자산 활용, 점진적 마이그레이션

이 분리를 통해 다음과 같은 이점을 얻었습니다.

  • 기존 로직 테스트를 건드리지 않고 안전하게 Vitest를 도입할 수 있음
  • View 테스트에서 Jest의 복잡한 변환 설정(ts-jest-mock-import-meta, dotenv 등)을 제거할 수 있음
  • 추후 전체 테스트를 Vitest로 통합할 수 있는 기반 마련

Vitest 환경 설정

그럼 이제 Vitest를 프로젝트에 추가하는 방법을 단계별로 살펴보겠습니다.

패키지 설치

다음 명령어를 실행해서 Vitest와 관련 패키지를 설치합니다.

yarn add -D vitest @vitest/ui happy-dom

각 패키지의 역할은 다음과 같습니다.

패키지역할
vitest테스트 러너 (Jest 호환 API 제공)
@vitest/ui테스트 결과를 브라우저 UI로 확인
happy-dom경량 DOM 환경 (jsdom 대체, 더 빠른 실행 속도)

happy-domjsdom보다 훨씬 빠른 DOM 구현체입니다. View 컴포넌트의 렌더링 테스트에 적합하며, 대부분의 DOM API를 지원합니다.

package.json 스크립트 추가

Vitest 실행을 위한 스크립트를 다음과 같이 package.json 파일에 추가합니다.

{
  "scripts": {
    "test": "TZ=Asia/Tokyo jest --watchAll",
    "test:ci": "TZ=Asia/Tokyo jest --ci --bail",
    "test:view": "TZ=Asia/Tokyo vitest",
    "test:view:ci": "TZ=Asia/Tokyo vitest run"
  }
}
  • test:view: 로컬 개발 시 watch 모드로 View 테스트 실행
  • test:view:ci: CI 환경에서 한 번만 실행 후 종료

기존 test, test:ci 스크립트는 Jest를 그대로 사용합니다. TZ=Asia/Tokyo는 날짜/시간 관련 테스트의 일관성을 위해 타임존을 고정했습니다.

Jest 설정 변경

Jest가 View 테스트를 실행하지 않도록 설정을 변경할 필요가 있습니다. jest.config.js 파일을 열고 다음과 같이 수정합니다.

// jest.config.js
export default {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  testMatch: [
    '**/controller/**/*.test.ts',
    '**/actions/**/*.test.ts',
    '**/utils/**/*.test.ts',
    '**/selectors/**/*.test.ts',
  ],
  testPathIgnorePatterns: ['/node_modules/', '/view/'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '^.+\\.svg$': 'jest-svg-transformer',
    '\\.(css|scss)$': 'identity-obj-proxy',
  },
  setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
  clearMocks: true,
};

주요 변경 사항은 다음과 같습니다.

  • dotenv import 제거: Vitest에서는 Vite가 환경 변수를 자동으로 처리하므로, Jest에서도 Actions 테스트에는 불필요해졌습니다.
  • testMatch 추가: controller/, actions/, utils/, selectors/ 디렉토리의 테스트만 실행하도록 제한했습니다.
  • testPathIgnorePatterns/view/ 추가: view/ 하위 파일을 명시적으로 제외합니다.
  • transform 설정 제거: ts-jest-mock-import-meta가 더 이상 필요 없어져 transform 설정 전체를 단순화했습니다.

이렇게 Jest의 테스트 범위를 좁히면, View 테스트가 Jest에서 실행되지 않아 두 러너 간의 충돌을 방지할 수 있습니다.

Vite + React + TypeScript 프로젝트에서 Jest를 설정하여 테스트 코드를 작성하는 방법에 대해서 궁금하신 분들은 이전 블로그인 [Vite] TypeScript 기반 React 프로젝트에 테스트 환경 구성하기을 참고해주세요.

기존 View 테스트 정리

Vitest 도입과 함께 기존의 Jest 기반 View 테스트를 정리했습니다.

스냅샷 테스트 삭제

기존 View 테스트는 다음과 같이 대부분 Jest 스냅샷 테스트로 작성되어 있었습니다.

// 변경 전: Jest 스냅샷 테스트
import { render } from '@testing-library/react';
import { ItemPanel } from '.';

test('render well', () => {
  const { container } = render(
    <ItemPanel
      label="label"
      value="value"
      showButton
      buttonLabel="변경"
      helpText="help"
    />
  );
  expect(container).toMatchSnapshot();
});

스냅샷 테스트에는 다음과 같은 문제가 있었습니다.

  • 유지보수 비용이 높음: HTML 구조가 조금만 바뀌어도 스냅샷이 깨지고, 61줄 이상의 스냅샷 diff를 리뷰해야 합니다.
  • 의미 있는 검증이 아님: “HTML이 이전과 같다”는 것만 확인할 뿐, 실제로 사용자에게 올바르게 보이는지 검증하지 못합니다.
  • 수동 Mock의 복잡성: 컴포넌트 간 의존성을 끊기 위해 __mocks__/ 디렉토리에 복잡한 Mock 파일을 작성해야 했습니다.

이러한 스냅샷 파일(__snapshots__/)과 __mocks__/ 디렉토리를 삭제하고, Vitest에서 새로 작성할 수 있는 기반을 마련했습니다.

// 변경 후: Vitest에서 새로 작성할 수 있는 기반
describe('ItemPanel', () => {
  test('test', () => {
    // 테스트 작성
  });
});

이 PR에서는 +1,139줄 추가, -10,134줄 삭제로 대량의 스냅샷과 Mock 코드를 정리했습니다.

CI 연동 (GitHub Actions)

기존 CI 워크플로우에 Vitest 실행 단계를 추가했습니다.

# .github/workflows/check_code_service_a.yml (변경 전)
- name: Test
  run: yarn test:ci:serviceA -- --shard=${{ matrix.shard }}/10
# .github/workflows/check_code_service_a.yml (변경 후)
- name: Test actions
  run: yarn test:ci:serviceA -- --shard=${{ matrix.shard }}/10
- name: Test view
  run: yarn test:view:ci:serviceA -- --shard=${{ matrix.shard }}/10

변경 사항은 간단합니다.

  • 기존 Test 단계를 Test actions로 이름 변경 (Jest 실행)
  • Test view 단계 추가 (Vitest 실행)
  • 두 단계 모두 --shard 옵션을 사용하여 병렬로 테스트를 분산 실행

Vitest도 Jest와 마찬가지로 --shard 옵션을 지원하므로, 기존 CI의 병렬 실행 전략을 그대로 활용할 수 있습니다.

정리

이 글에서는 기존 Jest 프로젝트에 Vitest를 점진적으로 도입하는 방법을 소개했습니다.

항목내용
도입 전략View 테스트는 Vitest, 로직 테스트는 Jest로 분리
Vitest 환경vitest + happy-dom + @vitest/ui
Jest 설정 변경testMatchtestPathIgnorePatterns로 View 테스트 제외
기존 테스트 정리스냅샷 테스트와 수동 Mock 삭제 (-10,134줄)
불필요한 의존성 제거dotenv, ts-jest-mock-import-meta 삭제
CI 연동GitHub Actions에 Test view 단계 추가, shard 병렬 실행 지원

새로운 도구를 도입할 때의 핵심은 **“한 번에 전부 바꾸지 않는다”**는 것입니다. 테스트 대상에 따라 러너를 분리하면, 기존 테스트 자산을 유지하면서도 새로운 도구의 이점을 누릴 수 있습니다. 추후 로직 테스트도 Vitest로 마이그레이션하면 Jest 의존성을 완전히 제거할 수 있습니다.

모노레포 환경에서 테스트 방침 수립 과정과 도구 선정에 대해서 궁금하신 분들은 이전 블로그인 [React] 모노레포 환경에서 공통 컴포넌트 테스트 방침을, Vitest를 활용한 VRT(Visual Regression Testing) 구축에 대해서 궁금하신 분들은 [React] Vitest로 컴포넌트 VRT + 접근성 테스트 환경 구축도 참고해 보시기 바랍니다.

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.



SHARE
Twitter Facebook RSS