При построении сложных схем валидации возникает необходимость логически объединять проверки: выполнять только часть тестов в определённых сценариях, запускать отдельные ветви валидации по условию, отключать некоторые группы правил или изолировать независимые наборы проверок. В библиотеке Vest для этого используются группы тестов.
Группы позволяют:
Основной механизм группировки строится вокруг функции
group().
groupimport { create, test, enforce, group } from 'vest';
const suite = create((data) => {
group('credentials', () => {
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
test('password', 'Пароль слишком короткий', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
});
В данном примере обе проверки входят в группу
credentials.
Имя группы:
Функция group() создаёт контекст выполнения. Все тесты
внутри callback автоматически связываются с указанной группой.
group('profile', () => {
// тесты принадлежат группе profile
});
Группа не изменяет сами тесты. Она лишь добавляет дополнительный уровень организации.
Крупные формы обычно делятся на смысловые секции.
const suite = create((data) => {
group('account', () => {
test('email', 'Email обязателен', () => {
enforce(data.email).isNotBlank();
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
group('profile', () => {
test('firstName', 'Введите имя', () => {
enforce(data.firstName).isNotBlank();
});
test('age', 'Возраст должен быть положительным', () => {
enforce(data.age).greaterThan(0);
});
});
});
Такой подход:
Одна и та же группа может быть объявлена несколько раз.
group('profile', () => {
test('firstName', 'Введите имя', () => {
enforce(data.firstName).isNotBlank();
});
});
group('profile', () => {
test('lastName', 'Введите фамилию', () => {
enforce(data.lastName).isNotBlank();
});
});
Все тесты объединяются в одну логическую группу
profile.
Это особенно полезно при:
Одно из важнейших применений групп — активация набора проверок только при выполнении условий.
const suite = create((data) => {
test('type', 'Укажите тип аккаунта', () => {
enforce(data.type).inside(['person', 'company']);
});
if (data.type === 'company') {
group('company', () => {
test('companyName', 'Введите название компании', () => {
enforce(data.companyName).isNotBlank();
});
test('taxId', 'Введите ИНН', () => {
enforce(data.taxId).isNotBlank();
});
});
}
});
Группа company будет существовать только для
корпоративных аккаунтов.
Одна из самых популярных задач — wizard forms.
group('step1', () => {
test('email', 'Введите email', () => {
enforce(data.email).isNotBlank();
});
test('password', 'Введите пароль', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
group('step2', () => {
test('firstName', 'Введите имя', () => {
enforce(data.firstName).isNotBlank();
});
test('lastName', 'Введите фамилию', () => {
enforce(data.lastName).isNotBlank();
});
});
group('step3', () => {
test('country', 'Укажите страну', () => {
enforce(data.country).isNotBlank();
});
test('city', 'Укажите город', () => {
enforce(data.city).isNotBlank();
});
});
Подобная организация:
Vest позволяет запускать только нужные группы.
suite(data, {
group: 'step2'
});
Теперь выполняются только тесты группы step2.
Это особенно важно для:
Можно запускать несколько секций одновременно.
suite(data, {
group: ['step1', 'step2']
});
Vest выполнит обе группы.
Подход полезен при:
При большом количестве тестов selective validation существенно уменьшает нагрузку.
suite(data);
Выполняются все проверки.
suite(data, {
group: 'payment'
});
Запускается только необходимая часть validation suite.
На крупных enterprise-формах разница может быть очень заметной.
Vest поддерживает вложенную структуру.
group('checkout', () => {
group('delivery', () => {
test('address', 'Введите адрес', () => {
enforce(data.address).isNotBlank();
});
});
group('payment', () => {
test('card', 'Введите номер карты', () => {
enforce(data.card).isNotBlank();
});
});
});
Такая структура помогает моделировать сложную предметную область.
Группы не обязательно должны соответствовать UI.
group('security', () => {
test('password', 'Слабый пароль', () => {
enforce(data.password).longerThanOrEquals(12);
});
test('twoFactor', 'Требуется 2FA', () => {
enforce(data.twoFactor).isTruthy();
});
});
Здесь группа отражает бизнес-смысл, а не визуальный блок интерфейса.
Validation logic часто выносится в отдельные модули.
function validateProfile(data) {
group('profile', () => {
test('firstName', 'Введите имя', () => {
enforce(data.firstName).isNotBlank();
});
test('lastName', 'Введите фамилию', () => {
enforce(data.lastName).isNotBlank();
});
});
}
const suite = create((data) => {
validateProfile(data);
});
Такой подход:
Имя группы может формироваться программно.
group(`step_${currentStep}`, () => {
test('value', 'Поле обязательно', () => {
enforce(data.value).isNotBlank();
});
});
Однако чрезмерная динамика способна усложнить отладку.
Обычно рекомендуется:
Группы работают и с async validation.
group('account', () => {
test(
'email',
'Email уже используется',
async () => {
const exists = await api.emailExists(data.email);
enforce(exists).isFalsy();
}
);
});
Изоляция асинхронных тестов по группам особенно полезна при:
onlyVest поддерживает selective field validation через
only().
Группы и only() можно комбинировать.
suite(data, {
only: ['email'],
group: 'account'
});
В этом случае:
account;email.skipАналогично можно исключать проверки.
suite(data, {
skip: ['taxId'],
group: 'company'
});
Проверки поля taxId будут пропущены.
create((data) => {
test(...);
test(...);
test(...);
test(...);
test(...);
test(...);
test(...);
});
Проблемы:
create((data) => {
group('account', () => {
// account validation
});
group('profile', () => {
// profile validation
});
group('security', () => {
// security validation
});
group('billing', () => {
// billing validation
});
});
Такой подход значительно улучшает архитектуру validation layer.
В крупных приложениях validation удобно строить вокруг feature-модулей.
validation/
account/
billing/
profile/
security/
Каждый модуль экспортирует собственную группу тестов.
export function billingValidation(data) {
group('billing', () => {
test('cardNumber', 'Введите карту', () => {
enforce(data.cardNumber).isNotBlank();
});
});
}
Один и тот же validation block можно использовать повторно.
function addressGroup(data) {
group('address', () => {
test('city', 'Введите город', () => {
enforce(data.city).isNotBlank();
});
test('street', 'Введите улицу', () => {
enforce(data.street).isNotBlank();
});
});
}
Применение:
create((data) => {
addressGroup(data);
});
Плохо:
group('form', () => {
// 200 тестов
});
Лучше:
group('profile', () => {});
group('billing', () => {});
group('security', () => {});
Плохо:
group('g1', () => {});
group('g2', () => {});
Лучше:
group('shipping', () => {});
group('payment', () => {});
Плохо:
group('a', () => {
group('b', () => {
group('c', () => {
group('d', () => {
});
});
});
});
Глубокая вложенность затрудняет понимание validation flow.
import { create, test, enforce, group } from 'vest';
export const registrationSuite = create((data) => {
group('account', () => {
test('email', 'Введите email', () => {
enforce(data.email).isNotBlank();
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
group('profile', () => {
test('firstName', 'Введите имя', () => {
enforce(data.firstName).isNotBlank();
});
test('lastName', 'Введите фамилию', () => {
enforce(data.lastName).isNotBlank();
});
});
group('agreement', () => {
test('terms', 'Необходимо согласие', () => {
enforce(data.terms).isTruthy();
});
});
});
registrationSuite(data, {
group: 'account'
});
registrationSuite(data, {
group: ['profile', 'agreement']
});
Каждая группа должна представлять:
Плохо:
group('misc', () => {
// всё подряд
});
Хорошо:
group('payment', () => {});
group('shipping', () => {});
group('notifications', () => {});
Имена групп становятся частью архитектуры приложения.
Желательно:
В крупных проектах группы превращаются в строительные блоки validation architecture.
Они позволяют: