Генерация тестовых данных

Схемы в Joi описывают структуру и ограничения данных, но сами по себе не решают задачу получения реалистичных входных наборов. При разработке и тестировании API, сервисов и бизнес-логики возникает необходимость в воспроизводимых и разнообразных тестовых данных, которые соответствуют правилам валидации. Схема начинает выступать не только как фильтр входных данных, но и как формальное описание их генерации.

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


Базовая фиксация значений через default и required

Встроенный механизм default задаёт значения, которые используются при отсутствии данных. В контексте тестирования это становится базовым способом стабилизации входных объектов.

import Joi from 'joi';

const schema = Joi.object({
  id: Joi.number().integer().default(1),
  name: Joi.string().default('test user'),
  active: Joi.boolean().default(true)
});

const result = schema.validate({});

При валидации пустого объекта формируется предсказуемая структура, которая может использоваться как основа для тестов.

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


Использование метода example для описания тестовых значений

Метод example() добавляет эталонные значения к схеме. Он не участвует в валидации, но используется для документации и генерации тестовых сценариев во внешних инструментах.

const userSchema = Joi.object({
  id: Joi.number().example(1001),
  email: Joi.string().email().example('user@mail.com'),
  role: Joi.string().valid('admin', 'user').example('admin')
});

Такие значения часто извлекаются генераторами OpenAPI или инструментами мокирования, которые строят тестовые данные на основе метаданных схемы.


Построение детерминированных объектов через композицию схем

Сложные структуры данных формируются через вложенные объекты и массивы. Генерация тестовых данных в таких случаях строится от корня схемы.

const addressSchema = Joi.object({
  city: Joi.string().default('Almaty'),
  zip: Joi.string().default('050000')
});

const userSchema = Joi.object({
  id: Joi.number().default(1),
  name: Joi.string().default('Alice'),
  address: addressSchema
});

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


Альтернативные ветви через alternatives

alternatives() создаёт вариативные структуры, что особенно важно для генерации разнообразных тестовых данных.

const schema = Joi.alternatives().try(
  Joi.string().email(),
  Joi.string().pattern(/^[0-9]+$/)
);

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


Ограничения генерации и условные схемы

Валидационные ограничения (min, max, length, pattern) одновременно служат правилами для генерации корректных данных.

const schema = Joi.object({
  username: Joi.string().min(3).max(10).alphanum(),
  age: Joi.number().min(18).max(65)
});

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


Интеграция с генераторами случайных данных

Joi не содержит встроенного полноценного генератора случайных данных, поэтому используется связка с внешними библиотеками, такими как faker-подобные генераторы.

Типовой подход заключается в разделении ответственности:

  • Joi описывает структуру и ограничения
  • генератор создаёт значения, удовлетворяющие этим ограничениям
import Joi from 'joi';
import { faker } from '@faker-js/faker';

const schema = Joi.object({
  id: Joi.number().integer(),
  email: Joi.string().email(),
  name: Joi.string()
});

function generateUser() {
  return {
    id: faker.number.int(),
    email: faker.internet.email(),
    name: faker.person.fullName()
  };
}

Далее результат может проверяться через .validate(), что обеспечивает соответствие генерации схеме.


Генерация массивов и вложенных структур

Массивы требуют отдельного подхода, так как каждый элемент должен соответствовать под-схеме.

const schema = Joi.object({
  tags: Joi.array().items(Joi.string().min(3)).min(1).max(5)
});

При генерации тестовых данных логика должна учитывать:

  • количество элементов в пределах min/max
  • соответствие каждого элемента под-схеме
  • согласованность вложенных объектов

Пример структуры:

const data = {
  tags: ['node', 'api', 'test']
};

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

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

const customJoi = Joi.extend((joi) => ({
  type: 'evenNumber',
  base: joi.number(),
  validate(value, helpers) {
    if (value % 2 !== 0) {
      return { value, errors: helpers.error('number.even') };
    }
  }
}));

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


Практические паттерны построения генераторов на основе схем

Схема начинает рассматриваться как дерево описания данных. Генерация выполняется рекурсивным обходом:

  • примитивы получают случайные значения с учётом ограничений
  • объекты рекурсивно обходятся по ключам
  • массивы наполняются элементами, соответствующими items
  • альтернативы выбираются случайно или по стратегии покрытия

Типовой подход:

function generateFromSchema(schema) {
  const description = schema.describe();
  // обход description и генерация структуры
}

Метод describe() используется как источник метаинформации о схеме и позволяет строить универсальные генераторы вне зависимости от конкретной структуры данных.


Ограничения подхода и практическая применимость

Генерация тестовых данных на основе Joi не является встроенной функцией библиотеки, поэтому всегда требует внешнего слоя логики. Основные ограничения:

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

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