
Jest 기본 사용법 입문 가이드

개요
Jest는 JavaScript와 TypeScript 환경에서 가장 널리 사용되는 테스트 프레임워크 중 하나로, 특히 React 프로젝트에서 사실상 표준처럼 활용됩니다. 설정이 간단하고, 테스트 러너, 단언 라이브러리, 모킹(mocking) 기능을 한 번에 제공한다는 점이 큰 장점입니다.

이 노트에서는 Jest를 처음 접하는 사람도 따라 할 수 있도록 설치부터 가장 자주 쓰는 기능까지 차근차근 살펴봅니다. 복잡한 설정이나 고급 기능보다는, "당장 프로젝트에서 단위 테스트를 쓰기 위한 최소한의 기초"에 초점을 맞춥니다.
Jest란 무엇인가
Jest는 Facebook(현재 Meta)이 만든 JavaScript 테스트 프레임워크로, Node.js 환경에서 돌아가며 프론트엔드·백엔드 모두 테스트할 수 있습니다. 별도의 assertion(단언) 라이브러리 없이도 expect 같은 문법을 기본으로 제공하고, 테스트 파일을 찾고 실행하는 러너 기능, 콘솔에서 보기 좋은 리포트, 코드 커버리지 측정까지 한 번에 지원합니다.
다른 테스트 프레임워크를 사용할 때 흔히 겪는 "러너 + assertion + mocking + 커버리지 도구를 따로 설치해서 맞춰 써야 하는" 번거로움을 줄여 주기 때문에, 처음 테스트를 도입할 때 특히 진입 장벽이 낮은 편입니다.
설치와 기본 실행
가장 단순한 설치 방법은 프로젝트에 devDependencies로 Jest를 추가하는 것입니다. Node.js와 npm이 설치되어 있다는 전제에서 다음과 같이 진행합니다.
npm install --save-dev jest또는 yarn을 사용한다면 다음과 같이 설치합니다.
yarn add --dev jest설치 후에는 package.json에 테스트 스크립트를 추가하는 것이 일반적입니다.
{
"scripts": {
"test": "jest"
}
}이제 터미널에서 npm test 또는 yarn test를 실행하면 __tests__ 폴더 안이나 파일 이름이 .test.js, .spec.js인 파일들을 Jest가 자동으로 찾아 테스트를 수행합니다.
첫 번째 테스트 작성하기
Jest 테스트 파일에서는 기본적으로 test(또는 it) 함수와 expect 함수를 사용합니다. 가장 작은 예제를 통해 구조를 이해하는 것이 좋습니다.
// sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;// sum.test.js
const sum = require('./sum');
test('두 숫자를 더한다', () => {
expect(sum(1, 2)).toBe(3);
});여기서 test 함수의 첫 번째 인자는 테스트 이름(설명)이고, 두 번째 인자는 실제 테스트 로직이 들어 있는 함수입니다. expect는 테스트 대상 값을 감싸고, 뒤에 이어지는 매처(matcher)인 toBe는 "이 값이 특정 값과 일치해야 한다"라는 조건을 표현합니다.
파일을 저장한 뒤 npm test를 실행하면 Jest가 sum.test.js를 찾아 실행하고 결과를 콘솔에 표시해 줍니다.
describe와 테스트 구조화
테스트가 많아지면 관련 테스트를 하나의 그룹으로 묶어 구조를 명확하게 만드는 것이 좋습니다. 이때 describe 함수를 사용합니다.
describe('sum 함수', () => {
test('양수 덧셈', () => {
expect(sum(1, 2)).toBe(3);
});
test('음수와 양수 덧셈', () => {
expect(sum(-1, 2)).toBe(1);
});
});describe 블록은 단순히 "이 그룹은 무엇을 테스트하는지"를 설명하고, 그 안에 여러 개의 test를 넣어 관련된 케이스들을 모아 둡니다. 테스트 리포트를 볼 때도 describe 이름이 함께 표시되므로, 많은 테스트를 관리할 때 도움이 됩니다.
기본 매처(expect) 사용법
Jest는 expect 뒤에 붙여 쓰는 다양한 매처를 제공합니다. 가장 기본적인 것은 다음과 같습니다.
toBe: 원시 값(숫자, 문자열, 불리언 등)의 정확한 일치 여부를 비교할 때 사용합니다.toEqual: 객체나 배열처럼 구조가 있는 값의 "내용이 같은지" 비교할 때 사용합니다.toBeNull,toBeUndefined,toBeDefined: 값이 null인지, undefined인지, 정의되어 있는지 확인합니다.toBeTruthy,toBeFalsy: 값이 JS에서 truthy/falsy로 평가되는지 확인할 때 씁니다.toContain: 배열이나 문자열에 특정 값이 포함되어 있는지 검사합니다.toThrow: 함수가 에러를 던지는지 확인할 때 사용합니다.
예를 들어 객체를 비교할 때는 toBe 대신 toEqual을 사용하는 것이 일반적입니다.
test('객체 비교', () => {
const user = { name: 'Kim', age: 20 };
expect(user).toEqual({ name: 'Kim', age: 20 });
});toBe를 사용하면 두 객체가 정확히 같은 인스턴스인지(참조가 같은지)를 비교하기 때문에, 내용만 같은 새 객체와 비교하면 실패합니다.
비동기 코드 테스트하기
현대 JavaScript 코드에서 비동기 처리는 매우 흔하므로, Promise나 async/await, 콜백 기반 코드 등을 테스트하는 법을 아는 것이 중요합니다.
가장 기본적인 방식은 테스트 함수에 async를 붙이고, 내부에서 await를 사용하는 것입니다.
test('비동기 함수 테스트', async () => {
const data = await fetchData(); // Promise를 반환한다고 가정
expect(data).toBe('ok');
});Promise를 직접 반환해도 됩니다. Jest는 테스트 함수가 반환하는 Promise가 resolve될 때까지 기다립니다.
test('Promise 반환 테스트', () => {
return fetchData().then(data => {
expect(data).toBe('ok');
});
});에러가 발생하는 경우를 테스트하려면 rejects 또는 toThrow와 함께 사용할 수 있습니다.
test('비동기 에러 테스트', async () => {
await expect(fetchDataWithError()).rejects.toThrow('Network error');
});콜백 기반 API를 테스트할 때는 테스트 함수의 인자로 done을 받아, 비동작이 완료된 시점에 done()을 호출해 주면 됩니다. done이 호출되지 않으면 테스트가 타임아웃으로 실패합니다.
beforeEach, afterEach로 공통 준비/정리하기
여러 테스트에서 같은 준비 작업을 반복해서 작성하면 테스트 코드가 지저분해질 수 있습니다. 이때 Jest의 훅(hook) 함수인 beforeEach, afterEach, beforeAll, afterAll을 사용해 공통 작업을 분리할 수 있습니다.
let db;
beforeAll(() => {
db = createTestDB(); // 한 번만 실행
});
beforeEach(() => {
db.reset(); // 각 테스트 전에 실행
});
afterAll(() => {
db.close(); // 전체 테스트 후 한 번 실행
});
test('첫 번째 테스트', () => {
// db를 사용한 테스트
});
test('두 번째 테스트', () => {
// db를 사용한 또 다른 테스트
});이렇게 하면 테스트마다 동일한 초기화 코드를 반복하지 않아도 되고, 리소스 정리도 한 곳에서 관리할 수 있습니다.
모킹(Mock) 기본 개념
테스트할 때 실제 네트워크 요청이나 데이터베이스 접근을 수행하면 테스트가 느려지거나 외부 환경에 따라 결과가 달라질 수 있습니다. 이런 경우에는 특정 모듈이나 함수, 타이머 등을 "가짜 구현"으로 교체하는 모킹 기능을 사용합니다.
가장 단순한 모킹은 jest.fn()으로 가짜 함수를 만드는 것입니다.
test('콜백이 호출되는지 확인', () => {
const callback = jest.fn();
doSomething(callback); // 내부에서 callback을 호출한다고 가정
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith('success');
});외부 모듈을 통째로 모킹하고 싶다면 jest.mock()을 사용합니다.
// api.js
export function fetchUser() {
// 실제로 네트워크 요청을 보낸다고 가정
}
// userService.test.js
import { fetchUser } from './api';
import { getUserName } from './userService';
jest.mock('./api');
test('getUserName이 fetchUser 결과를 사용', async () => {
fetchUser.mockResolvedValue({ name: 'Kim' });
const name = await getUserName();
expect(name).toBe('Kim');
expect(fetchUser).toHaveBeenCalled();
});이렇게 하면 테스트에서는 네트워크를 전혀 사용하지 않고도 원하는 응답을 강제로 흉내 낼 수 있습니다.
타이머와 시간 관련 코드 테스트
setTimeout, setInterval 등의 타이머를 사용하는 코드는 실제 시간만큼 기다리면 테스트가 느려집니다. Jest의 가상 타이머(fake timers)를 사용하면 시간을 강제로 "점프"시켜 빠르게 테스트할 수 있습니다.
jest.useFakeTimers();
test('1초 후 실행되는 함수 테스트', () => {
const callback = jest.fn();
runAfter1Second(callback);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});jest.useFakeTimers()로 가상 타이머를 활성화한 뒤, advanceTimersByTime 또는 runAllTimers 같은 함수를 사용해 원하는 만큼 시간이 흐른 것처럼 만들 수 있습니다.
Jest와 TypeScript 사용
TypeScript 프로젝트에서는 타입 정의와 트랜스파일 과정이 필요합니다. 대표적으로 ts-jest를 사용하면 Jest가 TypeScript 파일을 바로 이해할 수 있도록 도와줍니다.
npm install --save-dev ts-jest @types/jest설치 후 Jest 설정 파일(jest.config.js 또는 jest.config.cjs)에서 다음과 같이 설정합니다.
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};테스트 파일은 보통 *.test.ts 또는 *.spec.ts 형태로 작성하며, 나머지 사용법은 JavaScript와 거의 동일합니다. 타입 정의 패키지인 @types/jest 덕분에 expect, test 같은 전역 함수에도 타입 정보가 적용되어 IDE 자동완성이 잘 동작합니다.
코드 커버리지 확인하기
Jest는 별도 도구 없이도 코드 커버리지를 측정할 수 있습니다. --coverage 옵션을 붙여 테스트를 실행하면 됩니다.
npm test -- --coverage실행 결과로 각 파일별로 얼마나 많은 줄, 함수, 분기(branch)가 테스트되었는지 요약 테이블이 출력됩니다. 또한 coverage 디렉터리에 HTML 리포트가 생성되어, 브라우저에서 어느 부분이 테스트되지 않았는지 시각적으로 확인할 수 있습니다.
다만 커버리지 수치가 높다고 해서 테스트의 질이 무조건 높다고 볼 수는 없으므로, "커버리지를 올리는 것"보다 "중요한 로직을 충분히 검증하는지"를 우선으로 보는 것이 좋습니다.
Jest 사용 시 흔한 팁과 주의점
테스트 이름은 "무엇을 기대하는지"를 읽기만 해도 이해될 정도로 구체적으로 작성하는 것이 좋습니다. 예를 들어 "로그인 성공"보다는 "올바른 이메일과 비밀번호로 로그인하면 토큰을 반환한다"처럼 행동과 결과를 함께 적는 방식이 유용합니다.
또한 테스트는 서로 독립적이어야 합니다. 한 테스트가 전역 상태를 바꾸고, 그 상태를 다른 테스트가 기대하는 구조는 피하는 것이 좋습니다. 필요하다면 beforeEach에서 항상 초기 상태로 되돌리거나, 각 테스트에서 새 인스턴스를 생성해 사용하도록 설계합니다.
마지막으로, 외부 API나 DB 같은 의존성을 모킹할 때는 "실제 구현과 너무 동떨어지지 않았는지"를 가끔 점검해야 합니다. 모킹이 잘못된 가정 위에 있다면, 테스트는 통과하지만 실제 환경에서는 오류가 나는 상황이 생길 수 있기 때문입니다.
마무리
이 노트에서는 Jest의 기본 개념과 설치 방법, 첫 테스트 작성, 주요 매처, 비동기 코드 테스트, 모킹, 타이머, TypeScript와의 연동, 커버리지 사용까지 핵심적인 입문 내용을 살펴보았습니다. 여기까지 익히면 대부분의 JavaScript/TypeScript 프로젝트에서 기본적인 단위 테스트를 작성하고 실행하는 데 무리가 없습니다.
실전에서는 프로젝트 특성에 따라 더 세밀한 설정(Jest 설정 파일 커스터마이징, Babel 연동, React Testing Library와의 결합 등)이 필요할 수 있지만, 그때도 기본적인 사용법이 몸에 배어 있으면 문서를 보며 자연스럽게 확장해 나갈 수 있습니다.
모킹 더 깊게 이해하기
모킹(mocking)은 테스트 대상 코드 주변에 있는 의존성(네트워크 요청, DB, 타이머, 전역 객체 등)을 "가짜 구현"으로 바꿔서, 테스트를 빠르고 예측 가능하게 만드는 기법입니다. 실제로는 다양한 용어가 함께 쓰이는데, 대략적으로는 "결괏값만 고정하는 가짜 함수"는 스텁(stub), "호출 횟수·인자를 기록하는 가짜 함수"는 스파이(spy), "둘을 모두 포함하는 좀 더 포괄적인 개념"이 목(mock)이라고 볼 수 있습니다. Jest에서는 이 구분을 엄밀하게 나누지 않고, jest.fn, jest.spyOn, jest.mock 같은 API로 대부분의 테스트 더블(test double)을 만들 수 있습니다.
Jest의 핵심은 "목 함수(mock function)"입니다. jest.fn()은 아무 동작도 하지 않는 가짜 함수이지만, 호출 횟수(toHaveBeenCalledTimes), 인자(toHaveBeenCalledWith), 반환값 등을 모두 기록해 줍니다. 여기에 mockReturnValue, mockResolvedValue, mockImplementation 등을 조합해서 원하는 동작을 지정합니다.
const fn = jest.fn()
.mockReturnValueOnce('첫 호출')
.mockResolvedValueOnce('두 번째 호출은 Promise resolve')
.mockReturnValue('그 이후는 항상 이 값');
test('mock 함수 동작', async () => {
expect(fn()).toBe('첫 호출');
await expect(fn()).resolves.toBe('두 번째 호출은 Promise resolve');
expect(fn()).toBe('그 이후는 항상 이 값');
});이미 존재하는 실제 함수를 "감싸서" 호출 기록만 보고 싶다면 jest.spyOn을 사용합니다. 스파이는 기본적으로 원본 구현을 그대로 호출하지만, mockImplementation이나 mockReturnValue를 사용해 특정 테스트에서만 동작을 바꾸고, mockRestore()로 원래 구현을 되돌릴 수 있습니다.
import * as api from './api';
test('spyOn으로 부분 모킹', async () => {
const spy = jest.spyOn(api, 'fetchUser').mockResolvedValue({ name: 'Lee' });
const user = await api.fetchUser();
expect(user.name).toBe('Lee');
expect(spy).toHaveBeenCalled();
spy.mockRestore(); // 다른 테스트에 영향 주지 않도록 원복
});모듈 전체를 모킹하는 jest.mock('./module')은 테스트 파일 상단에서 한 번만 호출되며, 해당 모듈의 모든 export가 자동으로 가짜 함수로 바뀝니다. 이 기본 동작이 너무 단순하다면, 두 번째 인자로 "모듈 팩토리 함수"를 넘겨 세밀하게 원하는 모양의 목 구현을 정의할 수 있습니다. 한편, 모킹을 사용할 때는 테스트 간 상태가 섞이지 않도록 beforeEach에서 jest.clearAllMocks() 또는 jest.resetAllMocks()를 호출해 호출 기록·구현을 초기화하는 습관이 중요합니다. 마지막으로, "가능한 한 실제 구현을 그대로 쓰고, 외부 환경·I/O처럼 통제하기 어려운 부분만 모킹한다"는 원칙을 지키면 과도한 모킹으로 인해 테스트와 실제 동작이 괴리되는 문제를 줄일 수 있습니다.
