Тестирование через реальную границу — Примеры

Пример 1 — Подделка, вернувшая строки в другом порядке

Против подмены в памяти модульный тест проходит. Тот же сценарий на настоящем движке падает. Объясните, какое предположение выдумала подделка, и исправьте код.

// подделка: возвращает строки в порядке вставки, поэтому здесь всё проходит
test("lists orders newest first", async () => {
  const fake = { find: async () => [older, newer] };
  expect(await listOrders(fake)).toEqual([newer, older]);
});

// настоящий движок: FAIL   expected [newer, older], received [older, newer]
// в запросе нет ORDER BY; база вправе вернуть любой порядок

Решение

// проверяем на стыке: то, что вернуло настоящее хранилище
test("lists orders newest first", async () => {
  const db = await freshDatabase();          // настоящий движок, своя схема
  await db.insert("orders", [older, newer]);
  expect(await listOrders(db)).toEqual([newer, older]);
});

// теперь запрос говорит то, что предполагал тест
const rows = await db.query("select * from orders order by created_at desc");

Порядок вставки список в памяти сохранял потому, что так ведёт себя массив JavaScript, а реляционное хранилище без ORDER BY не обещает ничего. Это ровно тот класс предположений, ради которого и существует настоящая граница; изнутри подделки, разделяющей ваше заблуждение, его не поймать. Исправлять нужно запрос, а не тест: контракт, нужный вызывающему коду, сформулировал именно тест, а код ему не соответствовал.

Пример 2 — Тест, зависящий от настенных часов

Сервис регистрации тестируют через его репозиторий. Весь день утверждение проходит, а после полуночи падает у того, кто запустил набор. Назовите недетерминированную часть и сделайте тест устойчивым.

test("counts signups in the last 24 hours", async () => {
  const repo = await freshDatabase();
  const stats = new SignupStats(repo);
  await stats.record("a@example.test");
  expect(await stats.recentCount()).toBe(1);
});

// FAIL после полуночи: expected 1, received 0

Решение

const now = new Date("2026-03-01T12:00:00Z");
const repo = await freshDatabase();
const stats = new SignupStats(repo, { now: () => now });
await stats.recordAt("a@example.test", now);
expect(await stats.recentCount()).toBe(1);

// в бою ничего не передают и получают настоящие часы
new SignupStats(repo);

Настоящая инфраструктура обнажает каждое чтение часов в коде. Здесь же это чтение спрятано внутри сервиса. Если передать часы снаружи, утверждение привяжется к фиксированному мгновению, а не к часу, в который случайно запустился набор, — та же дисциплина, что и сортировка строк перед сравнением. Тест, который падает лишь в некоторые дни, учит команду перезапускать прогон вместо разбора; набор, которому не доверяют, стоит меньше того, который никто не написал.