Моки и фикстуры

Работа с тестовыми данными в связке с валидацией на основе схем строится вокруг идеи воспроизводимости и соответствия структуре. Библиотека Superstruct предоставляет механизмы описания структур данных, однако генерация моков и фикстур не является встроенной функциональностью в полном смысле. Это формирует отдельный слой архитектуры, который строится поверх схем.

Роль фикстур в схемо-ориентированной валидации

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

Ключевые характеристики фикстур:

  • соответствие структурным контрактам схем;
  • повторяемость при каждом запуске тестов;
  • отсутствие зависимости от внешних источников;
  • возможность композиции из вложенных структур.

Валидационные схемы Superstruct определяют форму данных, но не описывают их источник. Поэтому фикстуры формируются как отдельный слой, опирающийся на определения структур.

Базовое формирование тестовых объектов

Схемы Superstruct описывают типы данных декларативно:

import { object, string, number } from "superstruct";

const User = object({
  id: number(),
  name: string(),
  email: string()
});

На основе такой схемы фикстура создаётся вручную:

const userFixture = {
  id: 1,
  name: "Ivan Petrov",
  email: "ivan@example.com"
};

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

Фабрики как слой генерации фикстур

Фабрики позволяют инкапсулировать создание объектов, соответствующих схемам:

const createUser = (overrides = {}) => ({
  id: 1,
  name: "Ivan Petrov",
  email: "ivan@example.com",
  ...overrides
});

Такой подход обеспечивает:

  • переиспользование базовой структуры;
  • возможность частичного переопределения полей;
  • изоляцию логики генерации.

Фабрики особенно эффективны при тестировании API, где требуется вариативность входных данных при сохранении корректной структуры.

Связь фикстур и схем Superstruct

Superstruct используется для проверки корректности фикстур перед использованием в тестах:

import { assert } from "superstruct";

const user = createUser();

assert(user, User);

Таким образом достигается двойная гарантия:

  • структура соответствует контракту;
  • тестовые данные не нарушают ограничения схемы.

Генерация моков на основе схем

В отличие от фиксированных фикстур, моки часто требуют динамической генерации. Это реализуется через обход структуры схемы и подстановку значений по типу.

Простейшая стратегия генерации:

const mockString = () => "text";
const mockNumber = () => 1;

const generate = (struct) => {
  if (struct.type === "string") return mockString();
  if (struct.type === "number") return mockNumber();
  return null;
};

В реальных системах применяется более сложная логика, учитывающая:

  • ограничения (min, max, pattern);
  • вложенные структуры;
  • массивы и словари;
  • объединения (union types).

Работа с вложенными структурами

При использовании object и array фикстуры строятся рекурсивно:

const Post = object({
  id: number(),
  title: string(),
  author: User
});

Фикстура:

const postFixture = {
  id: 10,
  title: "Hello world",
  author: createUser()
};

Рекурсивная композиция позволяет поддерживать целостность сложных моделей данных без дублирования логики генерации.

Использование значений по умолчанию

Superstruct поддерживает расширения, позволяющие задавать значения по умолчанию:

import { defaulted, number } from "superstruct";

const Age = defaulted(number(), 18);

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

const userFixture = {
  id: 1,
  name: "Ivan"
  // age будет подставлен автоматически при обработке структуры
};

Моки с использованием случайных данных

Для повышения реалистичности тестирования часто применяется генерация псевдослучайных значений. Обычно используется внешняя библиотека генерации данных, а Superstruct выступает слоем проверки.

Пример интеграции:

const randomEmail = () => `user${Math.random()}@mail.com`;

const createUser = () => ({
  id: Math.floor(Math.random() * 1000),
  name: "User",
  email: randomEmail()
});

Такая стратегия применяется в нагрузочном тестировании и сценариях с высокой вариативностью входных данных.

Контроль детерминизма

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

  • фиксация seed генератора;
  • хранение заранее сгенерированных наборов;
  • изоляция генерации в отдельные модули.

Детерминированные фикстуры предпочтительнее в юнит-тестах, тогда как моки со случайностью применяются в интеграционных сценариях.

Объединение фикстур и union-типов

Superstruct поддерживает объединения типов, что усложняет генерацию тестовых данных:

const ID = union([string(), number()]);

Фикстуры в этом случае должны учитывать вариативность:

const idFixtureA = "abc-123";
const idFixtureB = 42;

Генерация моков требует стратегии выбора одного из допустимых типов с последующей генерацией соответствующего значения.

Переопределение полей и частичные фикстуры

Часто требуется создавать вариации одного и того же объекта:

const baseUser = createUser();

const adminUser = {
  ...baseUser,
  role: "admin"
};

Такой подход позволяет строить иерархию фикстур без дублирования базовых значений.

Применение в тестовых окружениях

Фикстуры и моки используются в различных типах тестирования:

  • юнит-тесты — строго детерминированные данные;
  • интеграционные тесты — частично вариативные структуры;
  • e2e-тесты — близкие к реальным сценариям данные.

Superstruct используется как механизм проверки корректности данных на каждом уровне, обеспечивая соответствие контрактам независимо от способа генерации.

Композиция фабрик и схем

Оптимальная архитектура тестовых данных строится на разделении:

  • схемы описывают структуру;
  • фабрики создают фикстуры;
  • моки обеспечивают вариативность;
  • Superstruct выполняет валидацию.

Такой подход позволяет масштабировать тестовую базу без нарушения целостности моделей данных и без дублирования логики генерации.