Unit-тестирование suite

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

Suite в Vest представляет собой контейнер, объединяющий набор тестов, сгруппированных по логическому признаку: сущность формы, поле, бизнес-правило или сценарий валидации. Каждый suite создаётся через функцию create и содержит набор вызовов test, которые выполняются последовательно или выборочно в зависимости от состояния выполнения.

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

import { create, test } from 'vest';

const loginSuite = create((data = {}) => {
  test('username', 'Имя пользователя обязательно', () => {
    return data.username && data.username.length > 0;
  });

  test('password', 'Пароль обязателен', () => {
    return data.password && data.password.length > 0;
  });
});

Каждый вызов test фиксирует:

  • идентификатор поля;
  • сообщение об ошибке;
  • функцию проверки, возвращающую булево значение или промис.

Поведение выполнения тестов

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

Основные принципы выполнения:

  • последовательная обработка тестов по порядку объявления;
  • немедленная фиксация ошибки при провале теста;
  • сохранение результатов для каждого поля отдельно;
  • возможность частичного пересчёта при изменении входных данных.
const result = loginSuite({
  username: '',
  password: '12345'
});

console.log(result.hasErrors());

Группировка тестов

Для сложных форм используется логическая группировка тестов через вложенные suite-конструкции. Это позволяет разделять ответственность между частями валидации и создавать модульные проверки.

const userSuite = create((data = {}) => {
  test('email', 'Некорректный email', () => {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email);
  });

  test('age', 'Возраст должен быть числом', () => {
    return typeof data.age === 'number';
  });

  const passwordSuite = create(() => {
    test('password', 'Слишком короткий пароль', () => {
      return data.password.length >= 8;
    });

    test('password', 'Пароль должен содержать цифру', () => {
      return /\d/.test(data.password);
    });
  });

  passwordSuite();
});

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

Условное выполнение тестов

Одним из ключевых механизмов suite является возможность условного запуска тестов. Это снижает нагрузку и исключает выполнение нерелевантных проверок.

import { create, test, skip } from 'vest';

const profileSuite = create((data = {}) => {
  test('type', 'Тип обязателен', () => {
    return data.type;
  });

  if (data.type !== 'admin') {
    skip('adminCode', 'Код администратора не требуется');
  } else {
    test('adminCode', 'Код администратора обязателен', () => {
      return data.adminCode && data.adminCode.length > 0;
    });
  }
});

Механизм skip фиксирует поле как пропущенное, сохраняя целостность отчёта, но не выполняя проверку.

Асинхронные тесты в suite

Suite поддерживает асинхронные проверки, что критично при работе с серверной валидацией, например проверкой уникальности email или username.

const asyncSuite = create((data = {}) => {
  test('email', 'Email уже используется', async () => {
    const res = await fetch(`/api/check-email?email=${data.email}`);
    const json = await res.json();
    return json.available === true;
  });
});

Асинхронные тесты интегрируются в общий поток выполнения, не нарушая структуру suite. Результат агрегируется после завершения всех промисов.

Контекст выполнения suite

Suite может использовать внешний контекст, передаваемый через замыкания. Это позволяет создавать переиспользуемые наборы проверок, зависящие от состояния приложения.

const createPasswordSuite = (minLength) =>
  create((data = {}) => {
    test('password', `Минимальная длина ${minLength}`, () => {
      return data.password.length >= minLength;
    });
  });

const strictSuite = createPasswordSuite(12);
const normalSuite = createPasswordSuite(8);

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

Повторное использование тестов

В больших приложениях логика валидации часто повторяется. Vest позволяет извлекать тесты в отдельные функции и переиспользовать их внутри разных suite.

const isNotEmpty = (field) => (value) =>
  value[field] && value[field].trim().length > 0;

const baseSuite = create((data = {}) => {
  test('firstName', 'Имя обязательно', () => isNotEmpty('firstName')(data));
  test('lastName', 'Фамилия обязательна', () => isNotEmpty('lastName')(data));
});

Это снижает дублирование и упрощает сопровождение.

Состояние и результаты suite

После выполнения suite возвращает объект результата, содержащий детализированное состояние всех тестов. Каждое поле имеет собственный статус, список ошибок и мета-информацию о выполнении.

Типичная структура результата включает:

  • hasErrors() — наличие ошибок;
  • isValid() — полная валидность данных;
  • getErrors(field) — ошибки конкретного поля;
  • getErrors() — все ошибки сразу.
const result = loginSuite({ username: '', password: '' });

if (result.hasErrors()) {
  console.log(result.getErrors('username'));
}

Кэширование и оптимизация

Suite оптимизирует выполнение тестов через кэширование результатов. При повторном запуске с теми же входными данными система может пропускать уже вычисленные проверки. Это особенно важно при использовании в UI-формах, где пересчёт происходит на каждый ввод символа.

Оптимизация работает на уровне:

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

Приоритет и порядок выполнения

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

test('email', 'Email обязателен', () => !data.email);

test('email', 'Email некорректен', () => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email);
});

В данном случае сначала проверяется наличие значения, затем его формат.

Композиция suite

Suite можно объединять, создавая композицию проверок. Это позволяет строить сложные системы валидации из независимых модулей.

const authSuite = create((data) => {
  loginSuite(data);
  passwordSuite(data);
});

const registrationSuite = create((data) => {
  authSuite(data);
  emailSuite(data);
});

Композиция повышает повторное использование и снижает связность между модулями.

Обработка ошибок и отказоустойчивость

Suite не прерывается при выбросе исключений внутри тестов. Ошибки перехватываются и преобразуются в результаты валидации, предотвращая разрушение цепочки выполнения.

test('apiCheck', 'Ошибка сервера', async () => {
  try {
    await riskyRequest();
    return true;
  } catch {
    return false;
  }
});

Такой подход делает систему устойчивой к внешним сбоям.

Интеграция suite в архитектуру приложения

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

Ключевые свойства интеграции:

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

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