При тестировании схем валидации на основе Vest ключевая сложность возникает там, где правила начинают зависеть от внешнего мира: API, базы данных, таймеров, случайных значений, конфигурационных сервисов. Любая такая зависимость делает тесты нестабильными, медленными и плохо воспроизводимыми. Для изоляции поведения используются два базовых механизма — моки и стабы.
Стаб (stub) подменяет реальную реализацию функции фиксированным поведением. Мок (mock) не только подменяет реализацию, но и фиксирует факт вызова, его параметры и количество обращений. В контексте Vest это особенно важно, так как валидационные цепочки часто зависят от асинхронных проверок.
Валидационные схемы в Vest строятся декларативно:
Типичный пример зависимости:
Такие сценарии нельзя тестировать напрямую без изоляции, так как:
Асинхронные проверки в Vest часто оформляются через test
с промисами:
import { create, test } from 'vest';
const suite = create((data) => {
test('email', 'Email уже используется', async () => {
const exists = await apiCheckEmail(data.email);
if (exists) {
throw new Error();
}
});
});
Чтобы сделать тест стабильным, функция apiCheckEmail
заменяется стабом:
import * as api from '../api';
jest.spyOn(api, 'apiCheckEmail').mockResolvedValue(false);
Такой подход фиксирует поведение зависимости:
Моки применяются там, где важно не только значение, но и сам факт вызова зависимости.
Пример: логирование ошибок валидации:
function logValidationError(field, message) {
analytics.track('validation_error', {
field,
message,
});
}
В схеме Vest:
import { create, test } from 'vest';
const suite = create(() => {
test('username', 'Ошибка', () => {
logValidationError('username', 'Ошибка');
throw new Error();
});
});
Тест с мокированием:
jest.mock('../analytics');
import analytics from '../analytics';
test('logs validation error', () => {
suite();
expect(analytics.track).toHaveBeenCalledWith(
'validation_error',
expect.objectContaining({
field: 'username',
})
);
});
Мок позволяет:
Использование моков и стабов в тестах Vest требует строгого разделения ролей:
Стабы:
Моки:
Нарушение этого разделения приводит к:
Vest-схема часто импортирует внешние зависимости:
import { create, test } from 'vest';
import { checkEmail } from './services/user';
import { sendMetric } from './metrics';
Для тестирования схема должна быть изолирована:
jest.mock('./services/user', () => ({
checkEmail: jest.fn(),
}));
jest.mock('./metrics', () => ({
sendMetric: jest.fn(),
}));
Далее управление поведением:
import { checkEmail } from './services/user';
checkEmail.mockResolvedValue(true);
Такой подход позволяет тестировать:
Особый случай — имитация ошибок API:
checkEmail.mockRejectedValue(new Error('Network error'));
В Vest это приводит к ветке ошибок:
test('email', 'Ошибка проверки', async () => {
try {
await checkEmail();
} catch (e) {
throw new Error('Ошибка проверки');
}
});
Тестирование такого поведения важно для:
Некоторые схемы используют задержки:
test('username', 'Слишком частые запросы', async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
return checkUsername();
});
Для тестов таймеры стабилизируются:
jest.useFakeTimers();
jest.advanceTimersByTime(500);
Это позволяет:
Vest позволяет группировать тесты, что приводит к множественным вызовам зависимостей:
group('auth', () => {
test('email', ...);
test('password', ...);
});
При использовании моков важно учитывать:
Пример проверки:
expect(checkEmail).toHaveBeenCalledTimes(2);
Распространённые проблемы:
Чрезмерное мокирование внутренних функций Приводит к тестированию реализации, а не поведения
Стабирование логики, которая должна быть частью теста Скрывает реальные дефекты
Неполное восстановление моков между тестами Создаёт утечки состояния
Мокирование всей схемы Vest вместо зависимостей Делает тест бессмысленным
При работе с Vest важно выстроить структуру:
зависимости выносятся в отдельные модули
API и побочные эффекты централизуются
бизнес-логика отделяется от инфраструктуры
тесты делятся на:
Моки и стабы становятся инструментом управления границами системы, а не средством подмены логики.
Сложные сценарии включают несколько зависимостей:
const exists = await checkEmail();
const allowed = await checkPermissions();
Стабирование:
checkEmail.mockResolvedValue(false);
checkPermissions.mockResolvedValue(true);
Это позволяет проверять:
Моки дают возможность анализировать не только результат, но и поведение:
Это особенно важно для сложных схем, где простого сравнения результата недостаточно и требуется понимание внутреннего исполнения.