스냅샷 테스트는 값의 출력을 저장하고 향후 테스트 실행과 비교합니다. 이는 UI 구성요소, 복잡한 객체 또는 일관되게 유지해야 하는 출력에 특히 유용합니다.
기본 스냅샷
스냅샷 테스트는 .toMatchSnapshot() 매처를 사용하여 작성합니다.
import { test, expect } from "bun:test";
test("snap", () => {
expect("foo").toMatchSnapshot();
});이 테스트가 처음 실행될 때 expect 에 대한 인수가 직렬화되어 테스트 파일 옆의 __snapshots__ 디렉토리에 있는 특별한 스냅샷 파일에 기록됩니다.
스냅샷 파일
위의 테스트를 실행한 후 Bun 이 생성합니다.
your-project/
├── snap.test.ts
└── __snapshots__/
└── snap.test.ts.snap스냅샷 파일에는 다음이 포함됩니다.
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`snap 1`] = `"foo"`;향후 실행에서는 인수가 디스크의 스냅샷과 비교됩니다.
스냅샷 업데이트
스냅샷은 다음 명령으로 다시 생성될 수 있습니다.
bun test --update-snapshots이는 다음 경우에 유용합니다.
- 출력을 의도적으로 변경한 경우
- 새 스냅샷 테스트를 추가하는 경우
- 예상 출력이 합리적으로 변경된 경우
인라인 스냅샷
더 작은 값의 경우 .toMatchInlineSnapshot() 을 사용하여 인라인 스냅샷을 사용할 수 있습니다. 이러한 스냅샷은 테스트 파일에 직접 저장됩니다.
import { test, expect } from "bun:test";
test("인라인 스냅샷", () => {
// 첫 실행: 스냅샷이 자동으로 삽입됩니다
expect({ hello: "world" }).toMatchInlineSnapshot();
});첫 실행 후 Bun 은 테스트 파일을 자동으로 업데이트합니다.
import { test, expect } from "bun:test";
test("인라인 스냅샷", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});인라인 스냅샷 사용
.toMatchInlineSnapshot()으로 테스트 작성- 한 번 테스트 실행
- Bun 이 스냅샷으로 테스트 파일을 자동으로 업데이트
- 이후 실행에서는 값이 인라인 스냅샷과 비교됩니다
인라인 스냅샷은 테스트 파일에서 예상 출력을 바로 보는 것이 유용한 작고 단순한 값에 특히 유용합니다.
오류 스냅샷
.toThrowErrorMatchingSnapshot() 및 .toThrowErrorMatchingInlineSnapshot() 을 사용하여 오류 메시지도 스냅샷할 수 있습니다.
import { test, expect } from "bun:test";
test("오류 스냅샷", () => {
expect(() => {
throw new Error("문제가 발생했습니다");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("다른 오류");
}).toThrowErrorMatchingInlineSnapshot();
});실행 후 인라인 버전은 다음과 같습니다.
test("오류 스냅샷", () => {
expect(() => {
throw new Error("문제가 발생했습니다");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("다른 오류");
}).toThrowErrorMatchingInlineSnapshot(`"다른 오류"`);
});고급 스냅샷 사용
복잡한 객체
스냅샷은 복잡한 중첩 객체와 잘 작동합니다.
import { test, expect } from "bun:test";
test("복잡한 객체 스냅샷", () => {
const user = {
id: 1,
name: "John Doe",
email: "john@example.com",
profile: {
age: 30,
preferences: {
theme: "dark",
notifications: true,
},
},
tags: ["developer", "javascript", "bun"],
};
expect(user).toMatchSnapshot();
});배열 스냅샷
배열도 스냅샷 테스트에 적합합니다.
import { test, expect } from "bun:test";
test("배열 스냅샷", () => {
const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
expect(numbers).toMatchSnapshot();
});함수 출력 스냅샷
함수의 출력을 스냅샷하세요.
import { test, expect } from "bun:test";
function generateReport(data: any[]) {
return {
total: data.length,
summary: data.map(item => ({ id: item.id, name: item.name })),
timestamp: "2024-01-01", // 테스트용 고정
};
}
test("리포트 생성", () => {
const data = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
];
expect(generateReport(data)).toMatchSnapshot();
});React 구성요소 스냅샷
스냅샷은 React 구성요소에 특히 유용합니다.
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";
function Button({ children, variant = "primary" }) {
return <button className={`btn btn-${variant}`}>{children}</button>;
}
test("Button 구성요소 스냅샷", () => {
const { container: primary } = render(<Button>클릭하세요</Button>);
const { container: secondary } = render(<Button variant="secondary">취소</Button>);
expect(primary.innerHTML).toMatchSnapshot();
expect(secondary.innerHTML).toMatchSnapshot();
});속성 매처
테스트 실행 간에 변경되는 값 (타임스탬프 또는 ID 등) 의 경우 속성 매처를 사용하세요.
import { test, expect } from "bun:test";
test("동적 값이 있는 스냅샷", () => {
const user = {
id: Math.random(), // 매 실행마다 변경
name: "John",
createdAt: new Date().toISOString(), // 이것도 변경
};
expect(user).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(String),
});
});스냅샷에는 다음이 저장됩니다.
exports[`동적 값이 있는 스냅샷 1`] = `
{
"createdAt": Any<String>,
"id": Any<Number>,
"name": "John",
}
`;커스텀 직렬화기
스냅샷에서 객체가 직렬화되는 방식을 커스터마이즈할 수 있습니다.
import { test, expect } from "bun:test";
// Date 객체용 커스텀 직렬화기
expect.addSnapshotSerializer({
test: val => val instanceof Date,
serialize: val => `"${val.toISOString()}"`,
});
test("커스텀 직렬화기", () => {
const event = {
name: "Meeting",
date: new Date("2024-01-01T10:00:00Z"),
};
expect(event).toMatchSnapshot();
});모범 사례
스냅샷을 작게 유지
// 좋음: 집중된 스냅샷
test("사용자 이름 포맷팅", () => {
const formatted = formatUserName("john", "doe");
expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});
// 피하기: 검토하기 어려운 거대한 스냅샷
test("전체 페이지 렌더링", () => {
const page = renderEntirePage();
expect(page).toMatchSnapshot(); // 수천 줄이 될 수 있음
});설명적인 테스트 이름 사용
// 좋음: 스냅샷이 무엇을 나타내는지 명확
test("USD 기호와 함께 통화 포맷팅", () => {
expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
// 피하기: 무엇을 테스트하는지 불명확
test("포맷 테스트", () => {
expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});관련 스냅샷 그룹화
import { describe, test, expect } from "bun:test";
describe("Button 구성요소", () => {
test("primary variant", () => {
expect(render(<Button variant="primary">클릭</Button>))
.toMatchSnapshot();
});
test("secondary variant", () => {
expect(render(<Button variant="secondary">취소</Button>))
.toMatchSnapshot();
});
test("disabled state", () => {
expect(render(<Button disabled>비활성화</Button>))
.toMatchSnapshot();
});
});동적 데이터 처리
// 좋음: 동적 데이터 정규화
test("API 응답 형식", () => {
const response = {
data: { id: 1, name: "Test" },
timestamp: Date.now(),
requestId: generateId(),
};
expect({
...response,
timestamp: "TIMESTAMP",
requestId: "REQUEST_ID",
}).toMatchSnapshot();
});
// 또는 속성 매처 사용
test("매처가 있는 API 응답", () => {
const response = getApiResponse();
expect(response).toMatchSnapshot({
timestamp: expect.any(Number),
requestId: expect.any(String),
});
});스냅샷 관리
스냅샷 변경 검토
스냅샷이 변경될 때 주의해서 검토하세요.
# 변경 사항 확인
git diff __snapshots__/
# 변경이 의도적이면 업데이트
bun test --update-snapshots
# 업데이트된 스냅샷 커밋
git add __snapshots__/
git commit -m "UI 변경 후 스냅샷 업데이트"사용되지 않는 스냅샷 정리
Bun 은 사용되지 않는 스냅샷에 대해 경고합니다.
Warning: 1 unused snapshot found:
my-test.test.ts.snap: "더 이상 존재하지 않는 오래된 테스트 1"스냅샷 파일에서 삭제하거나 사용 가능한 경우 정리 플래그로 테스트를 실행하여 사용되지 않는 스냅샷을 제거하세요.
대규모 스냅샷 파일 구성
대규모 프로젝트의 경우 스냅샷 파일을 관리하기 쉽게 유지하는 것을 고려하세요.
tests/
├── components/
│ ├── Button.test.tsx
│ └── __snapshots__/
│ └── Button.test.tsx.snap
├── utils/
│ ├── formatters.test.ts
│ └── __snapshots__/
│ └── formatters.test.ts.snap문제 해결
스냅샷 실패
스냅샷이 실패할 때 diff 가 표시됩니다.
- Expected
+ Received
Object {
- "name": "John",
+ "name": "Jane",
}일반적인 원인.
- 의도적인 변경 (
--update-snapshots로 업데이트) - 의도하지 않은 변경 (코드 수정)
- 동적 데이터 (속성 매처 사용)
- 환경 차이 (데이터 정규화)
플랫폼 차이
플랫폼별 차이에 유의하세요.
// Windows/Unix 간 경로가 다를 수 있음
test("파일 작업", () => {
const result = processFile("./test.txt");
expect({
...result,
path: result.path.replace(/\\/g, "/"), // 경로 정규화
}).toMatchSnapshot();
});