Next.js와 리액트 프로젝트를 위한 초고속 Vitest 테스트 환경 구축 완벽 가이드

Next.js 애플리케이션에 대한 테스트 설정을 구축하는 과정은 생각보다 훨씬 빠르고 간편한데요.

이번 글에서는 브라우저 기반 테스트의 무거운 오버헤드 없이, React Testing Library와 Vitest를 결합하여 번개처럼 빠른 컴포넌트 테스트 환경을 만드는 방법을 아주 상세하게 알아보고자 합니다.

개발을 하다 보면 전체 브라우저를 띄우는 번거로움 없이 Next.js 컴포넌트만 빠르게 테스트하고 싶었던 순간이 분명 있었을 텐데요.

Vitest는 바로 그런 개발자들의 니즈를 완벽하게 충족시켜 주는 도구입니다.

이 튜토리얼을 따라오시면 유지보수가 쉽고 실행 속도가 놀라울 정도로 빠른 테스트 환경을 갖추게 되는데요. 기본 설정부터 실무에서 바로 쓸 수 있는 확장 기능까지 꼼꼼하게 다뤄보겠습니다.

왜 하필 Vitest일까요?

Vitest는 차세대 빌드 도구인 Vite를 기반으로 만들어진 현대적인 테스트 러너인데요. 핫 모듈 교체(HMR)와 네이티브 ESM 지원을 통해 타의 추종을 불허하는 실행 속도를 자랑합니다.

기존에 많이 쓰던 Jest도 훌륭하지만 설정이 복잡하고 속도 면에서 아쉬움이 있었던 게 사실입니다.

Vitest를 React Testing Library와 함께 사용하면 실제 브라우저를 구동하지 않고도 컴포넌트를 가볍게 테스트할 수 있는데요.

이는 컴포넌트가 화면에 제대로 렌더링 되는지 확인하는 스모크 테스트(Smoke Tests)에 최적화된 조합이라고 볼 수 있습니다.

필수 패키지 설치하기

가장 먼저 개발 환경에 필요한 의존성 패키지들을 설치해야 하는데요. 터미널을 열고 프로젝트 루트 경로에서 아래 명령어를 입력해 주시면 됩니다.

npm install --save-dev vitest @vitejs/plugin-react happy-dom \
  @testing-library/react @testing-library/jest-dom @testing-library/user-event

여기서 설치한 패키지들이 각각 어떤 역할을 하는지 이해하는 것이 중요한데요.

각 패키지의 용도를 정확히 알면 나중에 문제가 생겼을 때 트러블슈팅하기가 훨씬 수월해집니다.

먼저 vitest는 Vite의 빠른 속도를 그대로 활용하는 테스트 러너 본체입니다.

Jest와 호환되는 API를 제공하기 때문에 기존에 Jest를 써보신 분들은 아주 익숙하게 느끼실 텐데요. 성능과 개발자 경험(DX) 면에서는 훨씬 뛰어납니다.

그다음 @vitejs/plugin-react는 테스트 파일 내에서 JSX와 TSX 문법을 변환해 주는 핵심 플러그인입니다.

이 플러그인이 없다면 Vitest는 리액트 컴포넌트 문법을 해석하지 못해 에러를 뿜어냅니다.

주목할 만한 것은 happy-dom인데요. 이는 jsdom보다 훨씬 가볍고 빠른 DOM 구현체입니다.

실제 브라우저 없이도 컴포넌트 렌더링 테스트를 수행하기 위한 충분한 DOM 기능을 제공하면서도 테스트 속도를 비약적으로 높여줍니다.

@testing-library/react는 리액트 컴포넌트를 가상으로 렌더링하고 DOM 요소를 쿼리 할 수 있게 도와주는 유틸리티입니다.

구현 세부 사항보다는 실제 사용자의 관점에서 테스트를 작성하도록 유도하는 철학을 담고 있습니다.

마지막으로 @testing-library/jest-domtoBeInTheDocument()toHaveTextContent() 같은 커스텀 매처를 제공하는데요.

이를 통해 테스트 코드를 마치 영어 문장처럼 읽기 쉽고 직관적으로 작성할 수 있게 됩니다.

추가로 제가 명령어에 슬쩍 포함시킨 @testing-library/user-event는 원문에는 없었지만, 실제 인터랙션 테스트를 위해서는 필수적인 패키지입니다.

Vitest 설정 파일 작성하기

이제 프로젝트의 루트 디렉터리에 vitest.config.ts 파일을 생성하여 설정을 잡아줄 차례인데요.

이 파일이 테스트 환경의 네비게이터 역할을 하게 됩니다.

import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'happy-dom',
    globals: true,
    setupFiles: ['./vitest.setup.ts'],
    coverage: {
      provider: 'v8',
    },
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './'),
    },
  },
});

설정 내용을 하나씩 뜯어보면 왜 이렇게 작성했는지 이해가 되실 텐데요.

plugins 배열에 리액트 플러그인을 넣어주어야 Vitest가 JSX/TSX 파일을 올바르게 변환할 수 있습니다.

test.environmenthappy-dom으로 설정한 부분이 성능의 핵심입니다.

무거운 jsdom 대신 가벼운 구현체를 사용하여 필요한 DOM API는 모두 챙기면서 속도는 챙기는 전략을 취했습니다.

test.globalstrue로 설정하면 테스트 파일마다 describe, it, expect 등을 일일이 임포트 하지 않아도 되는데요.

이는 Jest를 사용할 때와 동일한 경험을 제공하여 코드 작성을 더 편리하게 만들어줍니다.

setupFiles 옵션은 각 테스트 파일이 실행되기 전에 먼저 실행될 파일을 지정합니다.

우리는 여기서 커스텀 매처들을 전역으로 등록해 줄 예정입니다.

특히 resolve.alias 설정은 Next.js 프로젝트에서 매우 중요한데요.

tsconfig.json에서 설정한 경로 별칭(Path Alias)을 테스트 환경에서도 똑같이 인식할 수 있게 해 주기 때문입니다.

이를 통해 상대 경로 지옥에서 벗어나 @/components/Button과 같이 깔끔한 임포트 구문을 사용할 수 있습니다.

테스트 환경 셋업 파일 만들기

이제 앞서 설정 파일에서 지정했던 vitest.setup.ts 파일을 생성해 볼 텐데요.

이 파일은 테스트가 실행되기 전의 준비 운동을 담당합니다.

import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';

// 각 테스트가 끝날 때마다 DOM을 정리합니다.
afterEach(() => {
  cleanup();
});

가장 중요한 것은 @testing-library/jest-dom/vitest를 임포트 하는 것인데요.

이렇게 하면 Vitest의 expect 함수가 확장되어 DOM 관련 매처들을 사용할 수 있게 됩니다.

예를 들어 expect(element !== null).toBe(true)처럼 어색하게 검증하는 대신, expect(element).toBeInTheDocument()라고 명확하게 작성할 수 있게 됩니다.

이는 코드의 가독성을 높일 뿐만 아니라 유지보수성도 크게 향상시킵니다.

또한 cleanup 함수를 afterEach 훅에 등록해 두었는데요.

이는 테스트 케이스 하나가 끝날 때마다 가상 DOM에 마운트 된 컴포넌트를 지워주어, 다음 테스트에 영향을 주지 않도록 격리시키는 중요한 작업입니다.

첫 번째 테스트 작성하기 및 확장

이제 본격적으로 테스트 코드를 작성해 볼 텐데요. 테스트 파일은 pages 디렉터리가 아닌 별도의 __tests__ 폴더에 모아두는 것이 좋습니다.

만약 pages 폴더 안에 테스트 파일을 두면, Next.js 빌드 과정에서 페이지로 오인하여 에러가 발생할 수 있기 때문입니다.

여기서는 단순한 렌더링 테스트를 넘어, 사용자의 클릭 이벤트까지 검증하는 더 실무적인 예제를 다뤄보겠습니다.

// __tests__/Home.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import Home from '../pages/index'; // 경로는 실제 프로젝트 구조에 맞게 조정하세요.

// Next.js의 Image 컴포넌트는 테스트 환경에서 종종 문제를 일으키므로 모킹이 필요할 수 있습니다.
vi.mock('next/image', () => ({
  default: (props: any) => <img {...props} />,
}));

describe('Home Page', () => {
  it('카테고리 카드가 정상적으로 렌더링 되어야 한다', () => {
    render(<Home />);

    // 텍스트가 문서에 존재하는지 확인
    const heading = screen.getByText('My Homepage');
    expect(heading).toBeInTheDocument();
  });

  it('버튼 클릭 시 상호작용이 정상적으로 동작해야 한다', async () => {
    const user = userEvent.setup();
    render(<Home />);

    // 버튼을 찾아서 클릭 이벤트를 발생시킵니다.
    const button = screen.getByRole('button', { name: /자세히 보기/i });
    await user.click(button);

    // 클릭 후의 변화를 감지합니다 (예: 모달이 뜬다거나 텍스트가 바뀐다거나).
    // 여기서는 예시로 버튼이 여전히 존재하는지 확인합니다.
    expect(button).toBeInTheDocument();
  });
});

위의 코드는 기본적인 렌더링 테스트에 사용자 인터랙션 테스트를 더한 형태인데요.

render() 함수를 통해 컴포넌트를 가상 DOM에 마운트하고, screen 객체로 요소를 찾습니다.

특히 user-event를 사용한 부분에 주목해 주셨으면 합니다.

단순히 컴포넌트가 그려지는 것뿐만 아니라, 사용자가 버튼을 클릭했을 때의 동작을 async/await 문법과 함께 테스트함으로써 실제 사용 시나리오를 검증할 수 있습니다.

또한 vi.mock을 사용하여 Next.js 전용 컴포넌트인 next/image를 일반 img 태그로 모킹 했는데요.

Next.js 고유의 기능을 사용하는 컴포넌트들은 테스트 환경에서 종종 에러를 유발하므로, 이렇게 모킹 처리해 주는 것이 안정적인 테스트를 위한 팁입니다.

NPM 스크립트 추가 및 고급 기능 활용

마지막으로 package.json 파일에 테스트를 실행할 스크립트를 추가해야 하는데요.

기본 명령어 외에도 유용한 옵션들을 함께 설정해 보겠습니다.

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest run --coverage"
  }
}

test 스크립트는 모든 테스트를 한 번 실행하고 종료하는데요.

이는 CI/CD 파이프라인에서 자동화 테스트를 돌릴 때 주로 사용됩니다.

test:watch는 개발 모드에서 매우 유용한데요. 파일이 수정될 때마다 관련된 테스트만 자동으로 다시 실행해 주기 때문에 TDD(테스트 주도 개발)를 하기에 최적의 환경을 제공합니다.

제가 추가로 제안하는 test:ui는 Vitest의 강력한 기능 중 하나인데요.

브라우저 기반의 아름다운 UI를 통해 테스트 결과, 의존성 그래프, 콘솔 로그 등을 시각적으로 확인할 수 있어 디버깅할 때 정말 편리합니다.

또한 test:coverage 명령어를 통해 내 코드의 몇 퍼센트가 테스트되고 있는지 커버리지 리포트를 생성할 수 있습니다.

이는 프로젝트의 안정성을 정량적으로 파악하는 데 큰 도움이 됩니다.

마무리

지금까지 Next.js 환경에서 Vitest를 설정하고 활용하는 방법을 아주 깊이 있게 다뤄보았는데요.

이 설정은 한 번 해두면 두고두고 개발 생산성을 높여주는 든든한 자산이 됩니다.

브라우저를 띄우지 않고도 내 컴포넌트가 완벽하게 동작한다는 확신을 가질 수 있다는 것, 정말 매력적이지 않나요?

이제 여러분의 프로젝트에도 이 빠르고 강력한 테스트 환경을 적용해 보시길 바랍니다.