Функция test — базовый строительный блок библиотеки Vest. Именно через
неё описываются отдельные проверки данных формы, состояния или
бизнес-логики. Каждый вызов test создаёт независимый
сценарий валидации, который Vest отслеживает, выполняет и сохраняет в
результирующем состоянии suite.
Сигнатура функции:
test(fieldName, message, callback);
Базовая структура:
import { create, test, enforce } from 'vest';
const suite = create((data = {}) => {
test('username', 'Имя пользователя обязательно', () => {
enforce(data.username).isNotBlank();
});
});
fieldNameИмя поля, к которому относится проверка.
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/.+@.+\..+/);
});
Vest использует это имя:
Допускается использовать любые строковые идентификаторы:
test('profile.email', 'Ошибка email', () => {});
test('address.city', 'Город обязателен', () => {});
messageСообщение ошибки, которое будет возвращено при неуспешной проверке.
test('password', 'Пароль слишком короткий', () => {
enforce(data.password).longerThanOrEquals(8);
});
Сообщение может быть:
Пример динамического сообщения:
test(
'age',
`Возраст должен быть больше ${minAge}`,
() => {
enforce(data.age).greaterThan(minAge);
}
);
callbackФункция с логикой проверки.
test('email', 'Email обязателен', () => {
enforce(data.email).isNotBlank();
});
Если внутри callback возникает ошибка enforcement, тест считается проваленным.
test('firstName', 'Введите имя', () => {
enforce(data.firstName).isNotBlank();
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
test('age', 'Возраст должен быть больше 18', () => {
enforce(data.age).greaterThan(18);
});
Одно поле может иметь несколько независимых тестов.
test('password', 'Пароль обязателен', () => {
enforce(data.password).isNotBlank();
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
test('password', 'Пароль должен содержать цифру', () => {
enforce(data.password).matches(/\d/);
});
Vest сохраняет все ошибки отдельно.
Результат:
[
'Пароль обязателен',
'Минимум 8 символов',
'Пароль должен содержать цифру'
]
test поддерживает асинхронную валидацию.
test(
'email',
'Email уже используется',
async () => {
const response = await fetch('/api/check-email');
const result = await response.json();
enforce(result.available).isTruthy();
}
);
Vest автоматически отслеживает:
test(
'username',
'Имя пользователя занято',
async () => {
const response = await api.userExists(data.username);
enforce(response.exists).isFalsy();
}
);
testVest воспринимает ошибку как неуспешную проверку.
test('price', 'Некорректная цена', () => {
if (data.price < 0) {
throw new Error();
}
});
Однако предпочтительнее использовать enforce, поскольку
он создаёт предсказуемую и стандартизированную логику.
enforceФункция test почти всегда используется вместе с
enforce.
test('title', 'Название обязательно', () => {
enforce(data.title).isNotBlank();
});
test('tags', 'Добавьте хотя бы один тег', () => {
enforce(data.tags).isArray();
enforce(data.tags.length).greaterThan(0);
});
test('settings', 'Настройки обязательны', () => {
enforce(data.settings).isObject();
});
Проверки можно выполнять только при определённых условиях.
test('middleName', 'Минимум 2 символа', () => {
if (!data.middleName) {
return;
}
enforce(data.middleName).longerThanOrEquals(2);
});
test(
'confirmPassword',
'Пароли не совпадают',
() => {
enforce(data.confirmPassword).equals(data.password);
}
);
const requiredFields = [
'firstName',
'lastName',
'email'
];
requiredFields.forEach(field => {
test(field, 'Поле обязательно', () => {
enforce(data[field]).isNotBlank();
});
});
const rules = [
{
field: 'username',
min: 4
},
{
field: 'password',
min: 8
}
];
rules.forEach(rule => {
test(
rule.field,
`Минимум ${rule.min} символов`,
() => {
enforce(data[rule.field])
.longerThanOrEquals(rule.min);
}
);
});
const suite = create(data => {
test('email', 'Введите email', () => {
enforce(data.email).isNotBlank();
});
});
const result = suite({
email: ''
});
result.getErrors('email');
Результат:
['Введите email']
Vest хранит состояние каждого test.
result.hasErrors('email');
result.isPending('email');
result.isValid('email');
Одно из ключевых преимуществ Vest — selective validation.
const suite = create((data, fieldName) => {
only(fieldName);
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/.+@.+/);
});
test('password', 'Слишком короткий пароль', () => {
enforce(data.password)
.longerThanOrEquals(8);
});
});
Запуск:
suite(formData, 'email');
В этом случае выполнится только тест поля email.
function validateEmail(email) {
test('email', 'Email обязателен', () => {
enforce(email).isNotBlank();
});
test('email', 'Некорректный формат', () => {
enforce(email).matches(/.+@.+/);
});
}
Использование:
const suite = create(data => {
validateEmail(data.email);
});
Vest корректно работает с динамическими структурами.
data.items.forEach((item, index) => {
test(
`items.${index}.title`,
'Название обязательно',
() => {
enforce(item.title).isNotBlank();
}
);
});
Vest умеет предотвращать проблемы устаревших запросов.
test(
'username',
'Имя пользователя занято',
async () => {
const result = await api.checkUsername(
data.username
);
enforce(result.available).isTruthy();
}
);
Если пользователь быстро изменяет значение поля:
alex
alex1
alex12
alex123
Vest игнорирует устаревшие результаты предыдущих запросов.
skipskip(
data.isAdmin,
() => {
test(
'role',
'Роль обязательна',
() => {
enforce(data.role).isNotBlank();
}
);
}
);
omitWhenomitWhen(data.isGuest, () => {
test(
'password',
'Введите пароль',
() => {
enforce(data.password).isNotBlank();
}
);
});
warntest может создавать предупреждения вместо ошибок.
import { warn } from 'vest';
warn(
'password',
'Пароль слишком простой',
() => {
enforce(data.password)
.matches(/[A-Z]/);
}
);
Предупреждения:
Плохо:
test('email', 'Ошибка email', () => {
enforce(data.email).isNotBlank();
enforce(data.email).matches(/.+@.+/);
enforce(data.email.length).lessThan(50);
});
Недостатки:
Лучше:
test('email', 'Введите email', () => {
enforce(data.email).isNotBlank();
});
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/.+@.+/);
});
test('email', 'Максимум 50 символов', () => {
enforce(data.email.length).lessThan(50);
});
Нежелательно:
test('email', 'Ошибка', () => {
saveUser();
});
Тесты должны быть детерминированными и не изменять состояние приложения.
Плохо:
test('token', 'Ошибка', () => {
enforce(Math.random()).greaterThan(0.5);
});
Результат проверки должен быть предсказуемым.
Хорошая практика:
test('phone', 'Телефон обязателен', () => {
enforce(data.phone).isNotBlank();
});
test('phone', 'Некорректный формат телефона', () => {
enforce(data.phone).matches(phoneRegex);
});
Vest лучше читается при линейной организации.
test(...);
test(...);
test(...);
Слишком глубокая вложенность ухудшает поддержку кода.
function required(field, value) {
test(field, 'Поле обязательно', () => {
enforce(value).isNotBlank();
});
}
Применение:
required('email', data.email);
required('password', data.password);
testПри выполнении test библиотека:
Регистрирует проверку в текущем suite.
Привязывает тест к указанному полю.
Выполняет callback.
Отслеживает синхронный или асинхронный результат.
Сохраняет статус:
Обновляет aggregate state suite.
Именно поэтому test является центральным элементом всей
архитектуры Vest.