Skip to content

스냅샷 테스트는 값의 출력을 저장하고 향후 테스트 실행과 비교합니다. 이는 UI 구성요소, 복잡한 객체 또는 일관되게 유지해야 하는 출력에 특히 유용합니다.

기본 스냅샷 ​

스냅샷 테스트는 .toMatchSnapshot() 매처를 사용하여 작성합니다.

test.ts
ts
import { test, expect } from "bun:test";

test("snap", () => {
  expect("foo").toMatchSnapshot();
});

이 테스트가 처음 실행될 때 expect 에 대한 인수가 직렬화되어 테스트 파일 옆의 __snapshots__ 디렉토리에 있는 특별한 스냅샷 파일에 기록됩니다.

스냅샷 파일 ​

위의 테스트를 실행한 후 Bun 이 생성합니다.

directory structure
text
your-project/
├── snap.test.ts
└── __snapshots__/
    └── snap.test.ts.snap

스냅샷 파일에는 다음이 포함됩니다.

__snapshots__/snap.test.ts.snap
ts
// Bun Snapshot v1, https://bun.com/docs/test/snapshots

exports[`snap 1`] = `"foo"`;

향후 실행에서는 인수가 디스크의 스냅샷과 비교됩니다.

스냅샷 업데이트 ​

스냅샷은 다음 명령으로 다시 생성될 수 있습니다.

bash
bun test --update-snapshots

이는 다음 경우에 유용합니다.

  • 출력을 의도적으로 변경한 경우
  • 새 스냅샷 테스트를 추가하는 경우
  • 예상 출력이 합리적으로 변경된 경우

인라인 스냅샷 ​

더 작은 값의 경우 .toMatchInlineSnapshot() 을 사용하여 인라인 스냅샷을 사용할 수 있습니다. 이러한 스냅샷은 테스트 파일에 직접 저장됩니다.

test.ts
ts
import { test, expect } from "bun:test";

test("인라인 스냅샷", () => {
  // 첫 실행: 스냅샷이 자동으로 삽입됩니다
  expect({ hello: "world" }).toMatchInlineSnapshot();
});

첫 실행 후 Bun 은 테스트 파일을 자동으로 업데이트합니다.

test.ts
ts
import { test, expect } from "bun:test";

test("인라인 스냅샷", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});

인라인 스냅샷 사용 ​

  1. .toMatchInlineSnapshot() 으로 테스트 작성
  2. 한 번 테스트 실행
  3. Bun 이 스냅샷으로 테스트 파일을 자동으로 업데이트
  4. 이후 실행에서는 값이 인라인 스냅샷과 비교됩니다

인라인 스냅샷은 테스트 파일에서 예상 출력을 바로 보는 것이 유용한 작고 단순한 값에 특히 유용합니다.

오류 스냅샷 ​

.toThrowErrorMatchingSnapshot() 및 .toThrowErrorMatchingInlineSnapshot() 을 사용하여 오류 메시지도 스냅샷할 수 있습니다.

test.ts
ts
import { test, expect } from "bun:test";

test("오류 스냅샷", () => {
  expect(() => {
    throw new Error("문제가 발생했습니다");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("다른 오류");
  }).toThrowErrorMatchingInlineSnapshot();
});

실행 후 인라인 버전은 다음과 같습니다.

test.ts
ts
test("오류 스냅샷", () => {
  expect(() => {
    throw new Error("문제가 발생했습니다");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("다른 오류");
  }).toThrowErrorMatchingInlineSnapshot(`"다른 오류"`);
});

고급 스냅샷 사용 ​

복잡한 객체 ​

스냅샷은 복잡한 중첩 객체와 잘 작동합니다.

test.ts
ts
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();
});

배열 스냅샷 ​

배열도 스냅샷 테스트에 적합합니다.

test.ts
ts
import { test, expect } from "bun:test";

test("배열 스냅샷", () => {
  const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
  expect(numbers).toMatchSnapshot();
});

함수 출력 스냅샷 ​

함수의 출력을 스냅샷하세요.

test.ts
ts
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 구성요소에 특히 유용합니다.

test.ts
tsx
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 등) 의 경우 속성 매처를 사용하세요.

test.ts
ts
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),
  });
});

스냅샷에는 다음이 저장됩니다.

snapshot file
txt
exports[`동적 값이 있는 스냅샷 1`] = `
{
  "createdAt": Any<String>,
  "id": Any<Number>,
  "name": "John",
}
`;

커스텀 직렬화기 ​

스냅샷에서 객체가 직렬화되는 방식을 커스터마이즈할 수 있습니다.

test.ts
ts
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.ts
ts
// 좋음: 집중된 스냅샷
test("사용자 이름 포맷팅", () => {
  const formatted = formatUserName("john", "doe");
  expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});

// 피하기: 검토하기 어려운 거대한 스냅샷
test("전체 페이지 렌더링", () => {
  const page = renderEntirePage();
  expect(page).toMatchSnapshot(); // 수천 줄이 될 수 있음
});

설명적인 테스트 이름 사용 ​

test.ts
ts
// 좋음: 스냅샷이 무엇을 나타내는지 명확
test("USD 기호와 함께 통화 포맷팅", () => {
  expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

// 피하기: 무엇을 테스트하는지 불명확
test("포맷 테스트", () => {
  expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

관련 스냅샷 그룹화 ​

test.ts
ts
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.ts
ts
// 좋음: 동적 데이터 정규화
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),
  });
});

스냅샷 관리 ​

스냅샷 변경 검토 ​

스냅샷이 변경될 때 주의해서 검토하세요.

bash
# 변경 사항 확인
git diff __snapshots__/

# 변경이 의도적이면 업데이트
bun test --update-snapshots

# 업데이트된 스냅샷 커밋
git add __snapshots__/
git commit -m "UI 변경 후 스냅샷 업데이트"

사용되지 않는 스냅샷 정리 ​

Bun 은 사용되지 않는 스냅샷에 대해 경고합니다.

warning
txt
Warning: 1 unused snapshot found:
  my-test.test.ts.snap: "더 이상 존재하지 않는 오래된 테스트 1"

스냅샷 파일에서 삭제하거나 사용 가능한 경우 정리 플래그로 테스트를 실행하여 사용되지 않는 스냅샷을 제거하세요.

대규모 스냅샷 파일 구성 ​

대규모 프로젝트의 경우 스냅샷 파일을 관리하기 쉽게 유지하는 것을 고려하세요.

directory structure
text
tests/
├── components/
│   ├── Button.test.tsx
│   └── __snapshots__/
│       └── Button.test.tsx.snap
├── utils/
│   ├── formatters.test.ts
│   └── __snapshots__/
│       └── formatters.test.ts.snap

문제 해결 ​

스냅샷 실패 ​

스냅샷이 실패할 때 diff 가 표시됩니다.

diff
diff
- Expected
+ Received

  Object {
-   "name": "John",
+   "name": "Jane",
  }

일반적인 원인.

  • 의도적인 변경 (--update-snapshots 로 업데이트)
  • 의도하지 않은 변경 (코드 수정)
  • 동적 데이터 (속성 매처 사용)
  • 환경 차이 (데이터 정규화)

플랫폼 차이 ​

플랫폼별 차이에 유의하세요.

test.ts
ts
// Windows/Unix 간 경로가 다를 수 있음
test("파일 작업", () => {
  const result = processFile("./test.txt");

  expect({
    ...result,
    path: result.path.replace(/\\/g, "/"), // 경로 정규화
  }).toMatchSnapshot();
});

Bun by www.bunjs.com.cn 편집