Jest 완벽 가이드 | JavaScript 테스팅·Mocking·Coverage·실전 활용
이 글의 핵심
Jest로 JavaScript 테스트를 구현하는 완벽 가이드입니다. Test Suites, Matchers, Mocking, Coverage, Snapshot Testing까지 실전 예제로 정리했습니다.
실무 경험 공유: Jest를 도입하면서, 버그가 70% 감소하고 리팩토링이 안전해진 경험을 공유합니다.
들어가며: “테스트 작성이 어려워요”
실무 문제 시나리오
시나리오 1: 테스트 환경 설정이 복잡해요
Mocha, Chai, Sinon 등을 조합해야 합니다. Jest는 All-in-One입니다.
시나리오 2: Mocking이 어려워요
수동 Mock 작성이 번거롭습니다. Jest는 자동 Mocking을 제공합니다.
시나리오 3: 느린 테스트
순차 실행이 느립니다. Jest는 병렬 실행으로 빠릅니다.
1. Jest란?
핵심 특징
Jest는 JavaScript 테스팅 프레임워크입니다.
주요 장점:
- Zero Config: 즉시 사용 가능
- Fast: 병렬 실행
- Snapshot Testing: UI 회귀 테스트
- Mocking: 자동 Mock
- Coverage: 내장 커버리지
2. 설치 및 설정
설치
npm install -D jest @types/jest ts-jest
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.test.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
3. 기본 테스트
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
// src/utils/math.test.ts
import { add, subtract } from './math';
describe('Math Utils', () => {
test('add', () => {
expect(add(1, 2)).toBe(3);
expect(add(-1, 1)).toBe(0);
});
test('subtract', () => {
expect(subtract(5, 3)).toBe(2);
expect(subtract(0, 5)).toBe(-5);
});
});
4. Matchers
// 기본
expect(value).toBe(3);
expect(value).toEqual({ name: 'John' });
expect(value).not.toBe(5);
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3);
expect(value).toBeLessThan(5);
expect(value).toBeCloseTo(0.3);
// Strings
expect(value).toMatch(/pattern/);
expect(value).toContain('substring');
// Arrays
expect(array).toContain('item');
expect(array).toHaveLength(3);
// Objects
expect(object).toHaveProperty('key');
expect(object).toMatchObject({ name: 'John' });
// Exceptions
expect(() => fn()).toThrow();
expect(() => fn()).toThrow('Error message');
5. 비동기 테스트
Promises
test('async test', async () => {
const data = await fetchData();
expect(data).toEqual({ name: 'John' });
});
Callbacks
test('callback test', (done) => {
fetchData((data) => {
expect(data).toEqual({ name: 'John' });
done();
});
});
6. Mocking
함수 Mock
const mockFn = jest.fn();
mockFn('arg1', 'arg2');
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenCalledTimes(1);
// 리턴값 설정
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue({ name: 'John' });
모듈 Mock
// src/services/api.ts
export async function fetchUsers() {
const response = await fetch('/api/users');
return response.json();
}
// src/services/api.test.ts
import { fetchUsers } from './api';
jest.mock('./api');
test('fetchUsers', async () => {
(fetchUsers as jest.Mock).mockResolvedValue([
{ id: 1, name: 'John' },
]);
const users = await fetchUsers();
expect(users).toHaveLength(1);
expect(users[0].name).toBe('John');
});
7. Snapshot Testing
// src/components/Button.test.tsx
import { render } from '@testing-library/react';
import Button from './Button';
test('Button snapshot', () => {
const { container } = render(<Button label="Click me" />);
expect(container).toMatchSnapshot();
});
8. Setup & Teardown
describe('Database', () => {
beforeAll(async () => {
await db.connect();
});
afterAll(async () => {
await db.disconnect();
});
beforeEach(async () => {
await db.clear();
});
test('create user', async () => {
const user = await db.user.create({ name: 'John' });
expect(user.name).toBe('John');
});
});
9. React Testing Library 통합
npm install -D @testing-library/react @testing-library/jest-dom
// src/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Button from './Button';
test('Button renders', () => {
render(<Button label="Click me" />);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
test('Button click', () => {
const handleClick = jest.fn();
render(<Button label="Click me" onClick={handleClick} />);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
10. Coverage
npm test -- --coverage
결과
----------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
----------------------|---------|----------|---------|---------|
All files | 85.71 | 83.33 | 88.89 | 85.71 |
src/utils | 100 | 100 | 100 | 100 |
math.ts | 100 | 100 | 100 | 100 |
src/services | 75 | 66.67 | 80 | 75 |
api.ts | 75 | 66.67 | 80 | 75 |
----------------------|---------|----------|---------|---------|
정리 및 체크리스트
핵심 요약
- Jest: JavaScript 테스팅 프레임워크
- Zero Config: 즉시 사용 가능
- Fast: 병렬 실행
- Mocking: 자동 Mock
- Snapshot Testing: UI 회귀 테스트
- Coverage: 내장 커버리지
구현 체크리스트
- Jest 설치
- 기본 테스트 작성
- Matchers 활용
- 비동기 테스트
- Mocking 구현
- Snapshot Testing
- Coverage 확인
- CI/CD 통합
같이 보면 좋은 글
- Vitest 완벽 가이드
- React Testing Library 가이드
- Cypress 완벽 가이드
이 글에서 다루는 키워드
Jest, Testing, JavaScript, TypeScript, Unit Test, Mocking, Coverage
자주 묻는 질문 (FAQ)
Q. Vitest와 비교하면 어떤가요?
A. Jest가 더 성숙하고 생태계가 큽니다. Vitest는 더 빠르고 Vite와 통합이 좋습니다.
Q. React만 지원하나요?
A. 아니요, Vue, Angular, Node.js 등 모든 JavaScript 프로젝트에서 사용할 수 있습니다.
Q. 느리지 않나요?
A. 병렬 실행과 캐싱으로 빠릅니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, Facebook, Airbnb 등 대기업에서 사용하고 있습니다.