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 поддерживает асинхронные проверки, что критично при работе с серверной валидацией, например проверкой уникальности 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 может использовать внешний контекст, передаваемый через замыкания. Это позволяет создавать переиспользуемые наборы проверок, зависящие от состояния приложения.
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 возвращает объект результата, содержащий детализированное состояние всех тестов. Каждое поле имеет собственный статус, список ошибок и мета-информацию о выполнении.
Типичная структура результата включает:
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 можно объединять, создавая композицию проверок. Это позволяет строить сложные системы валидации из независимых модулей.
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, а результат синхронизируется с UI или состоянием приложения.
Ключевые свойства интеграции:
Модель работы suite позволяет использовать его как единственный источник истины для состояния валидности данных, исключая необходимость дублирования логики в компонентах интерфейса.