지금까지 여러 사이드 프로젝트를 진행했지만, Jest는 한 번도 제대로 사용해본 적이 없다. 처음에는 Jest 자체를 잘 몰랐고, 또 프로젝트가 어느 정도 완성된 상태에서 테스트 환경을 뒤늦게 도입하기가 너무 번거롭게 느껴졌기 때문이다. 하지만 채용 공고나 다양한 오픈소스 프로젝트를 보다 보면 Jest는 사실상 기본 도구처럼 자리 잡고 있다는 생각이 든다. 그래서 이번 포스팅을 통해 Jest를 체계적으로 학습해보려고 한다.
Jest는 자바스크립트 테스트 프레임워크로, React를 비롯해 대부분의 JavaScript/TypeScript 프로젝트에서 사용 가능한 범용 테스트 도구다. 메타(구 페이스북)에서 개발했으며, 설정이 간단하고 사용하기 쉬운 API를 제공한다는 점에서 프론트엔드 생태계의 사실상 표준으로 자리 잡고 있다.
Zero-Config 지향
별도의 복잡한 설정 없이 바로 테스트를 작성하고 실행할 수 있다.
빠른 실행 속도
테스트를 병렬로 실행하여 큰 프로젝트에서도 효율적이다.
강력한 Mocking 지원
네트워크 요청, 타이머, 모듈 등을 간단하게 mock 처리할 수 있어 독립적인 테스트 작성이 가능하다.
스냅샷 테스트 제공
UI 변경 여부를 자동으로 감지해 예상치 못한 변화를 빠르게 확인할 수 있다.
TypeScript 지원
ts-jest 같은 도구를 통해 TypeScript 환경에서도 자연스럽게 사용할 수 있다.
Jest는 코드가 예상대로 동작하는지 검증하고, 프로젝트의 품질을 유지해주는 테스트 도구다.
TypeScript + React 환경 기준으로 Jest를 세팅하는 과정을 정리해본다.
먼저 jest와 타입스크립트 환경에서 동작하기 위한 도구들을 설치한다.
npm install --save-dev jest ts-jest @types/jest
React 컴포넌트 테스트도 할 예정이라면 아래도 함께 설치한다
npm install --save-dev @testing-library/react @testing-library/jest-dom
아래의 명령어로 자동으로 설정 파일을 생성할 수도 있다.
npx ts-jest config:init
그러면 아래와 유사한 jest.config.js 파일이 생성된다
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "jsdom",
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/$1",
},
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};
설명:
preset: TypeScript 파일을 변환하기 위해 ts-jest 사용
testEnvironment: React 테스트를 위한 jsdom 환경
moduleNameMapper: 절대경로(@/components/...)를 Jest에서도 인식하도록
setupFilesAfterEnv: 테스트 실행 전에 공통 설정 로드
React UI를 테스트 하기위해선 setup파일을 아래와 같이 설정한다
//setupTests.ts
import { server } from "./mocks/server";
import "whatwg-fetch";
import "@testing-library/jest-dom"; // React UI 테스트
// 모든 테스트 시작 전에 서버를 켜기
beforeAll(() => server.listen());
// 각 테스트 끝난 뒤 핸들러 리셋 (다른 테스트에 영향 방지)
afterEach(() => server.resetHandlers());
// 모든 테스트 끝나면 서버 닫기
afterAll(() => server.close());
import "@testing-library/jest-dom"
이걸 통해 다음과 같은 matcher를 사용할 수 있다:
아래와 같이 간단한 덧셈함수를 작성한다
//test/sum.ts
export function sum(a: number, b: number) {
return a + b;
}
그리고 테스트 케이스를 작성후 npx jest test/sum 을 해보면
//test/sum.test.ts
import { sum } from "./sum";
describe("덧셈 테스트", () => {
test("1 + 2 = 3 이어야 한다", () => {
expect(sum(1, 2)).toBe(3);
});
test("3 + 3 = 6 이어야 한다", () => {
expect(sum(3, 3)).toBe(6);
});
});

이렇게 성공적으로 테스트가 완료된걸 확인할수 있다.
이번엔 리액트 컴포넌트 UI를 테스트해보자
// src/components/hello.tsx
export default function Hello() {
return <div>Hello World</div>;
}
똑같이 테스트 케이스를 작성, npx jest components/hello 실행
// src/components/Hello.test.tsx
import { render, screen } from "@testing-library/react";
import Hello from "./hello";
test("Hello 컴포넌트가 정상 렌더링된다", () => {
render(<Hello />);
expect(screen.getByText("Hello World")).toBeInTheDocument();
});

컴포넌트 UI 테스트도 성공적으로 되는걸 확인할수있다.
단순히 컴포넌트 UI만 테스트하면 뭔가 실용성도 떨어지고 재미도 없다.
그래서 실제 서비스처럼 API가 개입된 흐름을 테스트해보고 싶어지는데,
문제는 테스트 환경에서는 진짜 서버를 부를 수 없다는 점이다.
이럴 때 필요한 게 바로 MSW(Mock Service Worker)다.
MSW는 네트워크 요청을 가짜로 만들어주는 도구로,
백엔드가 없어도 fetch나 axios 요청에 원하는 응답을 돌려줄 수 있다.
API가 끼어있는 컴포넌트 테스트를 현실적으로 만들려면 MSW가 필수다.
MSW는 반드시 v1을 사용해야 Jest와 안정적으로 호환된다.
설치 시 명시적으로 v1 설치:
npm i -D msw@1
API 요청을 어떻게 가짜 응답할지 작성하는 곳이다.
원하면 여러 개의 API를 여기에서 등록할 수 있다.
import { rest } from "msw";
export const handlers = [
rest.get("https://example.com/api/user", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
name: "김철수",
})
);
}),
];
테스트 시작 시 서버 켜고, 테스트 종료 후 서버 끄기.
import { server } from "./mocks/server";
import "whatwg-fetch"; // fetch polyfill
import "@testing-library/jest-dom";
// 테스트 시작 전: 서버 ON
beforeAll(() => server.listen());
// 각 테스트 후: 핸들러 리셋
afterEach(() => server.resetHandlers());
// 전체 테스트 끝나면 서버 OFF
afterAll(() => server.close());
여기까지 작성하면 기본적인 설정은 끝이다
테스트 케이스를 작성해보자
위에서 작성했던것과 마찬가지로 컴포넌트를 만들어준다.
//components/primaryButton.tsx
import React, { useState } from "react";
export function PrimaryButton() {
const [user, setUser] = useState<{ name: string } | null>(null);
async function handleClick() {
const res = await fetch("https://example.com/api/user");
const data = await res.json();
setUser(data);
}
return (
<div>
<button onClick={handleClick}>유저 불러오기</button>
{user && <p data-testid="username">{user.name}</p>}
</div>
);
}
테스트 케이스 작성, npx jest components/primaryButton 실행
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { PrimaryButton } from "./primaryButton";
test("버튼 클릭 시 유저 이름이 표시된다", async () => {
render(<PrimaryButton />);
// 버튼 클릭
await userEvent.click(screen.getByText("유저 불러오기"));
// Mock 응답 결과가 렌더링되는지 확인
const username = await screen.findByTestId("username");
expect(username).toHaveTextContent(/^김철수$/);
});

성공적으로 pass가 되는걸 확인할수 있다.
버튼을 클릭하면 실제 API 주소로 fetch를 보내지만, 테스트 환경에서는 이 요청이 인터넷으로 나가지 않는다. 대신 MSW 가 네트워크 요청을 가로채서 우리가 정의한 가짜 응답을 대신 돌려준다. 테스트 환경에서는 Service Worker(혹은 Node 환경용 인터셉터)가 fetch 호출을 중간에서 받아주고, 매칭되는 핸들러가 있으면 그 응답을 반환한다.
즉, 컴포넌트는 진짜 API를 호출한다고 생각하지만
실제로는MSW가 해당 URL을 intercept → mock response를 반환하는 구조다.
이 원리 덕분에 서버 없이도 “API 포함 UI 흐름”을 완전히 재현하며 테스트할 수 있다.
Jest를 활용한 기본적인 테스트 케이스 작성부터 MSW 를 이용한 API mocking까지 간단히 살펴보았다. 아직은 겉핥기식으로 경험해본 수준이지만, 이 정도 흐름만 이해해도 앞으로 더 복잡한 컴포넌트나 비동기 로직을 테스트하는 데 큰 도움이 될 것이다.
이제 실제 프로젝트에 적용해보면서 테스트 커버리지도 넓히고, 다양한 상황을 시뮬레이션하는 더 견고한 테스트 환경을 만들어볼 계획이다.