「프론트엔드 테스트 제대로 하기」 시리즈의 3편입니다. 전체 목차 보기 · 용어집

React + Vite + TypeScript 프로젝트에 테스트 환경을 붙이는 과정을 빈 폴더에서 첫 통과까지 통째로 따라 하는 글입니다. Vitest·Testing Library·jsdom을 설치하고, 설정 파일을 줄 단위로 이해하고, 첫 테스트를 초록불로 만들고, 세팅에서 자주 막히는 지점까지 — 이 글 하나로 끝나요.

시리즈로 따라오고 계셨다면, 0장에서 ‘왜’와 ‘무엇을’을 챙겼으니 이제 1장 — 직접 만드는 단계입니다. 이 편부터 하나의 데모 앱(사용자를 검색하는 작은 대시보드)에 테스트를 얹어가요. 검색으로 처음 오셨어도 괜찮습니다. 어차피 빈 프로젝트에서 시작하니, 그대로 따라오시면 됩니다. 오늘의 목표는 단순해요. 첫 초록불 하나 켜기.

실습 코드: 이 편의 상태가 step-03 태그에 그대로 담겨 있습니다. git clonegit checkout step-03으로 따라오세요. 완성본이 궁금하면 main을 보면 됩니다.

이번 편에서 배울 것

  • Vitest·Testing Library·jsdom 설치와 설정
  • npm test로 첫 테스트 실행
  • 설정에서 자주 막히는 지점

준비물: Node.js 18 이상, 터미널, 코드 에디터(VS Code 등). 터미널에서 node -v가 버전을 출력하면 준비 끝입니다.

npm test를 실행한 터미널 화면 - Vitest v4가 src/App.test.tsx의 '앱 제목이 보인다' 테스트 1개를 초록색 체크 표시와 함께 통과시키고, Test Files 1 passed와 Duration 746ms를 출력했습니다
npm test를 실행한 터미널 화면 - Vitest v4가 src/App.test.tsx의 '앱 제목이 보인다' 테스트 1개를 초록색 체크 표시와 함께 통과시키고, Test Files 1 passed와 Duration 746ms를 출력했습니다

프로젝트와 도구 설치

Vite의 React + TypeScript 템플릿에서 시작합니다. 첫 명령이 프로젝트 뼈대를 만들어주고, 두 번째 줄에서 그 폴더로 들어가 기본 의존성을 설치해요.

bash
npm create vite@latest frontend-testing-lab -- --template react-ts
cd frontend-testing-lab && npm install

여기에 테스트 도구를 더합니다. -D는 “개발할 때만 쓰는 패키지"라는 표시예요(배포되는 앱 코드에는 포함되지 않습니다).

bash
npm i -D vitest @vitest/coverage-v8 jsdom \
  @testing-library/react @testing-library/jest-dom @testing-library/user-event

한 번에 여섯 개나 설치하니 겁먹기 쉬운데, 각자 맡은 일이 하나씩이라 생각보다 단순합니다.

데이터 표
패키지하는 일
vitest테스트를 찾아서 실행하고 결과를 알려주는 러너 — 오늘의 주인공
jsdomNode 안에서 브라우저 화면(DOM)을 흉내 내는 가짜 브라우저
@testing-library/react컴포넌트를 그리고(render), 화면에서 요소를 찾는(screen) 도구
@testing-library/jest-domtoBeInTheDocument 같은 화면 검증용 매처 모음
@testing-library/user-event클릭·타이핑을 재현하는 도구 (나중에 본격 등장)
@vitest/coverage-v8커버리지 리포트 생성기 (나중에 사용)

이 중 핵심 네 도구가 겹쳐서 일하는 모습을 그림으로 보면 이렇습니다.

테스트 도구들의 중첩 구조 다이어그램 - 가장 바깥에 테스트를 찾아 실행하는 러너 Vitest, 그 안에 브라우저 화면을 흉내 내는 jsdom, 다시 그 안에 컴포넌트를 그리고 요소를 찾는 Testing Library와 화면 검증 매처를 더하는 jest-dom이 나란히 놓여 있고, 아래에서 vite.config.ts 파일 하나가 전체를 설정한다는 화살표가 연결되어 있습니다
테스트 도구들의 중첩 구조 다이어그램 - 가장 바깥에 테스트를 찾아 실행하는 러너 Vitest, 그 안에 브라우저 화면을 흉내 내는 jsdom, 다시 그 안에 컴포넌트를 그리고 요소를 찾는 Testing Library와 화면 검증 매처를 더하는 jest-dom이 나란히 놓여 있고, 아래에서 vite.config.ts 파일 하나가 전체를 설정한다는 화살표가 연결되어 있습니다

왜 Jest가 아니라 Vitest냐고요? Vite 프로젝트라면 Vitest가 자연스러운 선택이에요. 앱이 쓰는 Vite 설정을 그대로 재사용하고, Jest와 API가 거의 호환되며, 빠릅니다.


설정 파일

vite.config.ts 하나에 앱과 테스트 설정을 함께 둡니다. (데모 repo의 실제 설정입니다.) 처음엔 옵션이 많아 보이지만, 지금 이해해야 할 것은 주석이 달린 두 줄 — environmentglobals — 뿐이에요.

ts
/// <reference types="vitest/config" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',          // 브라우저 DOM 흉내
    globals: true,                 // it/expect를 import 없이
    setupFiles: './src/test/setup.ts',
    css: false,
    exclude: ['e2e/**', 'node_modules/**', 'dist/**'], // E2E는 Playwright 몫
    coverage: { provider: 'v8', reporter: ['text', 'html'] },
  },
})

setupFiles에 지정한 셋업 파일은 모든 테스트가 시작되기 전에 한 번씩 실행되는 준비 코드예요. 지금은 jest-dom 매처(화면 검증용 비교 도구들)를 등록하는 한 줄이 전부입니다. (나중에 MSW 설정이 여기에 추가돼요.)

ts
// src/test/setup.ts
import '@testing-library/jest-dom/vitest'

package.json 스크립트도 정리합니다.

json
"scripts": {
  "test": "vitest run",
  "test:watch": "vitest",
  "coverage": "vitest run --coverage",
  "typecheck": "tsc -p tsconfig.test.json --noEmit"
}

vitest run은 한 번 실행하고 끝나는 모드, vitest(run 없이)는 watch 모드입니다. watch 모드로 켜두면 파일을 저장할 때마다 관련 테스트가 자동으로 다시 돌아요 — 코드 고치고, 저장하고, 초록불 확인하고. 이 리듬이 테스트와 친해지는 지름길이라, 개발 중에는 npm run test:watch를 켜두는 걸 추천합니다. (typecheck에 나오는 tsconfig.test.json은 아직 안 만들었는데, 바로 아래 “자주 막히는 지점"에서 왜 필요한지와 함께 다룹니다.)


첫 테스트

테스트를 쓰기 전에 잠깐 — Vite 템플릿이 만들어준 기본 App.tsx(로고 돌아가는 그 화면)를 우리 데모 앱의 시작점으로 바꿔둡니다. 이걸 건너뛰면 잠시 뒤의 테스트가 빨간불이 되니 꼭 해주세요.

tsx
// src/App.tsx — 오늘은 이 정도면 충분합니다
export default function App() {
  return (
    <main>
      <h1>대시보드</h1>
      <p>프론트엔드 테스트 연습용 데모 앱입니다.</p>
    </main>
  )
}

(데모 repo의 App에는 사용자 검색 컴포넌트까지 들어 있지만, 그건 나중에 함께 만듭니다. 오늘은 제목 하나면 돼요.)

이제 가장 단순한 테스트로 파이프라인이 도는지 확인합니다. 파일은 src/App.test.tsx — 테스트할 파일 바로 옆에, 이름 끝에 .test.tsx를 붙여 만듭니다. Vitest는 이 패턴의 파일을 자동으로 찾아서 실행해요.

tsx
import { it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import App from './App'

it('앱 제목이 보인다', async () => {
  render(<App />)

  expect(screen.getByRole('heading', { level: 1, name: '대시보드' })).toBeInTheDocument()
})

처음 보는 문법을 하나씩 풀어볼게요.

  • it('설명', 함수)테스트 한 개를 정의합니다. 첫 인자의 문장이 실패 리포트에 그대로 뜨니, “무엇을 확인하는지"를 사람 말로 적어두세요.
  • render(<App />) — 컴포넌트를 가짜 브라우저(jsdom)에 실제로 그립니다.
  • screen.getByRole(...) — 그려진 화면에서 요소를 찾습니다. 여기서는 “레벨 1 제목이면서 이름이 ‘대시보드’인 것"을 찾았어요.
  • expect(값).매처() — “이 값이 이래야 한다"는 선언입니다. toBeInTheDocument처럼 점 뒤에 붙는 비교 방법을 **매처(matcher)**라고 불러요.

it으로 정의하고 expect로 선언한다 — 이 두 문장이 앞으로 시리즈 내내 우리의 기본 문형입니다.

bash
npm test
#  ✓ src/App.test.tsx (1 test)
#
#  Test Files  1 passed (1)
#       Tests  1 passed (1)

초록 체크가 떴다면 성공입니다. 만약 빨간불이라면 — 당황하지 말고 에러 메시지 첫 줄을 읽어보세요. 대부분 바로 아래 “자주 막히는 지점” 중 하나입니다.


자주 막히는 지점

  • document is not definedenvironment: 'jsdom' 누락
  • toBeInTheDocument가 없다는 타입 에러 → setup에서 @testing-library/jest-dom/vitest import 누락
  • E2E 파일까지 Vitest가 집는 문제exclude: ['e2e/**']
  • 빌드(tsc)가 테스트 파일까지 검사tsconfig.app.jsonexcludesrc/**/*.test.tsx, e2e 추가
  • 그럼 테스트 파일의 타입 오류는 누가 잡지? → 좋은 질문이에요. 빌드에서 뺐으니 그대로 두면 아무도 안 잡습니다. 테스트 전용 tsconfig.test.json을 만들고 "typecheck": "tsc -p tsconfig.test.json --noEmit" 스크립트를 추가하세요. 데모 repo에서 이 설정을 넣자마자 숨어 있던 타입 이슈 2건이 바로 잡혔습니다. CI에도 한 줄이면 들어가요.

마지막 두 항목(tsconfig 분리)은 처음이라면 어렵게 느껴질 수 있어요. 지금 완전히 이해하지 못해도 괜찮습니다 — 첫 초록불에는 영향이 없고, 실제로 그 문제에 부딪히면 그때 이 목록으로 돌아오면 됩니다. 데모 repo에는 전부 설정돼 있으니 비교해볼 수도 있고요.


벌써 접근성이 등장했어요. getByRole('heading', ...)은 “제목이 렌더됐나"와 “스크린 리더가 제목으로 인식하나"를 동시에 검증합니다. 이 이야기는 뒤에서 깊게 다룰 예정이에요.


한 장 요약

  • 설치는 두 줄: Vite React-TS 템플릿 생성 + npm i -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event @vitest/coverage-v8 — 여섯 개지만 각자 맡은 일이 하나씩
  • 설정은 vite.config.ts 하나에: 지금 이해할 건 environment: 'jsdom'(가짜 브라우저)과 globals: true 두 줄
  • setupFiles의 셋업 파일엔 jest-dom 등록 한 줄 — 화면 검증용 매처가 열립니다
  • 테스트 파일은 대상 파일 옆에 *.test.tsxit('설명', ...)으로 정의하고 expect(값).매처()로 선언
  • 빨간불이면 에러 첫 줄부터 읽고, 위의 “자주 막히는 지점” 목록과 대조

첫 초록불을 켰습니다

환경이 준비됐습니다. 다음 편부터 본격적으로 단위 테스트의 기초 체력을 길러볼게요.

첫 초록불이 켜지는 순간의 도파민은 진짜입니다. PASS ✓ 한 줄 보려고 우리는 이 고생을… 아니, 이 즐거움을 하는 거죠.

레벨업: 테스트 환경을 맨손으로 세팅하고 첫 초록불을 켤 수 있습니다.

다음 편: 단위 테스트 기초 — 순수 함수·경계값·AAA 패턴

낯선 용어가 있었다면 — 용어집에 전부 한 줄씩 정리돼 있어요.