Skip to content

Тестирование снимков сохраняет значение и сравнивает его с будущими запусками тестов. Это особенно полезно для UI-компонентов, сложных объектов или любого вывода, который должен оставаться согласованным.

Базовые снимки

Тесты снимков пишутся с использованием матчера .toMatchSnapshot():

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

test("снимок", () => {
  expect("foo").toMatchSnapshot();
});

Первый раз, когда этот тест запускается, аргумент expect будет сериализован и записан в специальный файл снимков в директории __snapshots__ рядом с тестовым файлом.

Файлы снимков

После запуска теста выше Bun создаст:

text
ваш-проект/
├── snap.test.ts
└── __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(). Эти снимки хранятся прямо в вашем тестовом файле:

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

test("встроенный снимок", () => {
  // Первый запуск: снимок будет вставлен автоматически
  expect({ hello: "world" }).toMatchInlineSnapshot();
});

После первого запуска Bun автоматически обновляет ваш тестовый файл:

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

test("встроенный снимок", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});

Использование встроенных снимков

  1. Напишите ваш тест с .toMatchInlineSnapshot()
  2. Запустите тест один раз
  3. Bun автоматически обновит ваш тестовый файл снимком
  4. При последующих запусках значение будет сравниваться со встроенным снимком

Встроенные снимки особенно полезны для небольших простых значений, где полезно видеть ожидаемый вывод прямо в тестовом файле.

Снимки ошибок

Вы также можете сохранять снимки сообщений об ошибках, используя .toThrowErrorMatchingSnapshot() и .toThrowErrorMatchingInlineSnapshot():

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

test("снимок ошибки", () => {
  expect(() => {
    throw new Error("Что-то пошло не так");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Другая ошибка");
  }).toThrowErrorMatchingInlineSnapshot();
});

После запуска встроенная версия становится:

ts
test("снимок ошибки", () => {
  expect(() => {
    throw new Error("Что-то пошло не так");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Другая ошибка");
  }).toThrowErrorMatchingInlineSnapshot(`"Другая ошибка"`);
});

Продвинутое использование снимков

Сложные объекты

Снимки хорошо работают со сложными вложенными объектами:

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

Снимки массивов

Массивы также хорошо подходят для тестирования снимков:

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

test("снимок массива", () => {
  const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
  expect(numbers).toMatchSnapshot();
});

Снимки вывода функций

Сохраняйте снимки вывода функций:

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-компонентов:

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), используйте матчеры свойств:

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

Снимок сохранит:

txt
exports[`snapshot with dynamic values 1`] = `
{
  "createdAt": Any<String>,
  "id": Any<Number>,
  "name": "John",
}
`;

Пользовательские сериализаторы

Вы можете настроить, как объекты сериализуются в снимках:

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

// Пользовательский сериализатор для объектов Date
expect.addSnapshotSerializer({
  test: val => val instanceof Date,
  serialize: val => `"${val.toISOString()}"`,
});

test("пользовательский сериализатор", () => {
  const event = {
    name: "Встреча",
    date: new Date("2024-01-01T10:00:00Z"),
  };

  expect(event).toMatchSnapshot();
});

Лучшие практики

Держите снимки небольшими

ts
// Хорошо: Сфокусированные снимки
test("форматирование имени пользователя", () => {
  const formatted = formatUserName("john", "doe");
  expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});

// Избегайте: Огромные снимки, которые трудно рецензировать
test("отрисовка всей страницы", () => {
  const page = renderEntirePage();
  expect(page).toMatchSnapshot(); // Это может быть тысячи строк
});

Используйте описательные имена тестов

ts
// Хорошо: Ясно, что представляет снимок
test("форматирует валюту с символом USD", () => {
  expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

// Избегайте: Непонятно, что тестируется
test("тест форматирования", () => {
  expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

Группируйте связанные снимки

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

describe("компонент Button", () => {
  test("вариант primary", () => {
    expect(render(<Button variant="primary">Нажми</Button>))
      .toMatchSnapshot();
  });

  test("вариант secondary", () => {
    expect(render(<Button variant="secondary">Отмена</Button>))
      .toMatchSnapshot();
  });

  test("состояние disabled", () => {
    expect(render(<Button disabled>Отключено</Button>))
      .toMatchSnapshot();
  });
});

Обработка динамических данных

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 предупредит о неиспользуемых снимках:

txt
Warning: 1 unused snapshot found:
  my-test.test.ts.snap: "old test that no longer exists 1"

Удалите неиспользуемые снимки, удалив их из файлов снимков или запустив тесты с флагами очистки, если доступно.

Организация больших файлов снимков

Для больших проектов рассмотрите организацию тестов для поддержания управляемости файлов снимков:

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

Устранение неполадок

Неудачи снимков

Когда снимки не совпадают, вы увидите diff:

diff
- Ожидается
+ Получено

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

Распространённые причины:

  • Намеренные изменения (обновите с --update-snapshots)
  • Ненамеренные изменения (исправьте код)
  • Динамические данные (используйте матчеры свойств)
  • Различия окружения (нормализуйте данные)

Различия платформ

Будьте внимательны к различиям платформ:

ts
// Пути могут отличаться между Windows/Unix
test("операции с файлами", () => {
  const result = processFile("./test.txt");

  expect({
    ...result,
    path: result.path.replace(/\\/g, "/"), // Нормализация путей
  }).toMatchSnapshot();
});

Bun от www.bunjs.com.cn