Middleware для тестов

Архитектура валидации в библиотеке Vest строится вокруг декларативных правил, которые выполняются в контексте “сессии” тестов. Каждый набор правил (suite) представляет собой последовательность проверок, а middleware выступает промежуточным слоем, влияющим на выполнение, наблюдение и модификацию процесса тестирования.

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


Место middleware в архитектуре Vest

Внутренний процесс работы Vest можно представить как последовательность шагов:

  1. Инициализация тестовой сессии
  2. Выполнение набора правил (suite)
  3. Сбор результатов (errors, warnings, skipped)
  4. Агрегация состояния
  5. Возврат результата валидации

Middleware внедряется между этими этапами и может:

  • изменять входные данные до выполнения тестов
  • влиять на порядок выполнения правил
  • добавлять побочные эффекты (логирование, метрики)
  • прерывать выполнение при определённых условиях
  • модифицировать результат после выполнения suite

Базовая модель middleware

Middleware в Vest можно рассматривать как функцию следующего вида:

(context, next) => {
  // логика до выполнения тестов

  const result = next();

  // логика после выполнения тестов

  return result;
}

Где:

  • context — объект текущей тестовой сессии
  • next — функция продолжения цепочки middleware
  • result — результат выполнения текущего этапа

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

Контекст содержит ключевую информацию о текущем тесте:

  • имя поля (field)
  • текущее значение
  • набор правил
  • состояние выполнения (pending, done, skipped)
  • накопленные ошибки
  • метаданные suite

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

{
  field: "email",
  value: "user@example.com",
  tests: ["required", "email"],
  errors: [],
  meta: {
    suiteName: "loginForm",
    index: 2
  }
}

Middleware может как читать, так и изменять эти данные.


Типы middleware в Vest

1. Pre-processing middleware

Исполняется до запуска тестов.

Основные задачи:

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

Пример:

const trimMiddleware = (ctx, next) => {
  if (typeof ctx.value === "string") {
    ctx.value = ctx.value.trim();
  }

  return next();
};

Такой middleware гарантирует, что все тесты работают с очищенными данными, снижая количество ложных ошибок.


2. Validation middleware

Встраивается непосредственно в процесс выполнения правил.

Используется для:

  • динамического изменения набора тестов
  • условного пропуска проверок
  • добавления зависимых правил

Пример условного пропуска:

const skipIfEmptyMiddleware = (ctx, next) => {
  if (ctx.value === "") {
    ctx.skip = true;
    return ctx;
  }

  return next();
};

3. Post-processing middleware

Выполняется после завершения всех тестов.

Задачи:

  • преобразование результата
  • агрегация ошибок
  • форматирование вывода
  • логирование итогов

Пример:

const errorFormatterMiddleware = (ctx, next) => {
  const result = next();

  return {
    ...result,
    errors: result.errors.map(e => ({
      message: e,
      field: ctx.field
    }))
  };
};

Цепочка middleware

Middleware в Vest формируют цепочку выполнения, аналогичную middleware-цепочкам в серверных фреймворках. Каждый слой получает управление и обязан передать его дальше через next().

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

  1. Pre-middleware
  2. Основные тесты
  3. Post-middleware

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


Асинхронные middleware

Vest поддерживает асинхронные тесты, поэтому middleware также может быть асинхронным.

const asyncLoggerMiddleware = async (ctx, next) => {
  await logToServer(ctx.field);

  const result = await next();

  await logResult(result);

  return result;
};

Особенности:

  • обязательное ожидание next()
  • возможность блокировать выполнение до завершения внешних операций
  • влияние на производительность всей suite

Управление потоком выполнения

Middleware может вмешиваться в поток выполнения тестов:

Прерывание выполнения

const stopOnFirstError = (ctx, next) => {
  const result = next();

  if (result.errors.length > 0) {
    ctx.abort = true;
  }

  return result;
};

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

const conditionalMiddleware = (ctx, next) => {
  if (ctx.meta.suiteName === "strictMode") {
    return next();
  }

  ctx.skip = true;
  return ctx;
};

Интеграция middleware в suite

Middleware подключается на уровне набора тестов:

suite("email", () => {
  use(trimMiddleware);
  use(skipIfEmptyMiddleware);

  test("required", () => {
    enforce(value => value.length > 0);
  });

  test("format", () => {
    enforce(value => value.includes("@"));
  });
});

Каждый вызов use() добавляет middleware в текущий стек выполнения.


Порядок исполнения нескольких middleware

Если подключено несколько middleware, они выполняются последовательно:

use(A);
use(B);
use(C);

Фактический порядок:

  • A → B → C → tests → C → B → A

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


Ошибки и обработка исключений

Middleware должен учитывать возможные ошибки в тестах:

  • синхронные исключения
  • ошибки асинхронных операций
  • некорректные состояния контекста

Пример безопасного middleware:

const safeMiddleware = async (ctx, next) => {
  try {
    return await next();
  } catch (err) {
    ctx.errors.push(err.message);
    return ctx;
  }
};

Производительность middleware

Middleware напрямую влияет на скорость выполнения suite.

Ключевые факторы:

  • количество слоёв
  • наличие асинхронных операций
  • тяжёлые преобразования данных
  • частота вызовов внешних API

Рекомендации архитектурного характера:

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

Middleware и изоляция тестов

В Vest каждая suite должна оставаться изолированной. Middleware может нарушить изоляцию, если:

  • изменяет глобальное состояние
  • использует внешние переменные без контроля
  • кеширует данные между suite

Корректная модель:

const isolatedMiddleware = (ctx, next) => {
  const localState = { ...ctx.meta };

  const result = next();

  return result;
};

Композиция middleware

Middleware можно комбинировать для создания сложных цепочек поведения:

const composedMiddleware = compose([
  trimMiddleware,
  normalizeMiddleware,
  loggingMiddleware
]);

Композиция позволяет:

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

Middleware как инструмент расширения Vest

За счёт middleware Vest превращается из простой библиотеки валидации в расширяемую систему правил, где поведение определяется не только тестами, но и окружением выполнения.

Через middleware можно реализовать:

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