В библиотеке Vest формирование сообщений об ошибках строится вокруг идеи декларативных правил и контекстной генерации текста. Сообщение не является вторичным побочным эффектом — оно считается частью правила, которое может зависеть от входных данных, состояния группы и внешнего контекста выполнения.
Каждое правило в Vest может возвращать результат в виде проваленной проверки с текстом ошибки. Базовая форма:
test('username', 'Username is required', () => {
enforce(value).isNotEmpty();
});
В этом случае строка "Username is required" является
статическим сообщением. Оно фиксировано и не зависит от входных
данных.
Сообщение привязывается к конкретному тесту и сохраняется в структуре результатов, где ключом выступает имя поля и идентификатор проверки.
Часто требуется формировать текст ошибки на основе входного значения. Vest поддерживает передачу функции вместо строки:
test('password', () => {
enforce(value)
.longerThan(8)
.message(() => `Пароль слишком короткий: текущая длина ${value.length}`);
});
Функция сообщения выполняется только при провале теста, что снижает избыточные вычисления и позволяет использовать актуальные данные состояния.
В Vest существует механизм контекста, который позволяет передавать внешние параметры в набор тестов. Это особенно важно для локализации и бизнес-правил.
vest.use((data) => ({
lang: data.lang,
}));
test('email', () => {
const { lang } = vest.getContext();
enforce(value).matches(/@/).message(() =>
lang === 'ru'
? 'Некорректный email'
: 'Invalid email'
);
});
Контекст позволяет централизованно управлять формулировками без дублирования логики.
Vest организует тесты в группы, что влияет на структуру хранения ошибок. Каждое сообщение относится не только к полю, но и к группе валидации.
group('auth', () => {
test('email', 'Email invalid', () => {
enforce(value).matches(/@/);
});
test('password', 'Password invalid', () => {
enforce(value).longerThan(8);
});
});
Сообщения внутри группы могут агрегироваться и использоваться для отображения блока ошибок формы.
.message()Метод .message() позволяет отделить правило проверки от
текста ошибки:
test('age', () => {
enforce(value)
.isNumber()
.message('Возраст должен быть числом');
enforce(value)
.greaterThanOrEquals(18)
.message('Возраст должен быть не меньше 18 лет');
});
Каждое условие может иметь собственное сообщение, даже внутри одного теста.
Сообщения могут зависеть от условий, включая тип ошибки или состояние данных:
test('username', () => {
enforce(value)
.isNotEmpty()
.message(() => (value ? '' : 'Поле обязательно'));
enforce(value)
.longerThan(3)
.message(() =>
value.length < 3
? 'Слишком короткое имя'
: 'Ошибка имени'
);
});
Такая модель позволяет строить многоуровневую логику без внешних обработчиков.
Локализация реализуется через вынесение словарей и использование контекста или внешних функций:
const messages = {
ru: {
required: 'Поле обязательно',
email: 'Неверный email',
},
en: {
required: 'Field is required',
email: 'Invalid email',
},
};
test('email', () => {
const { lang } = vest.getContext();
enforce(value)
.matches(/@/)
.message(messages[lang].email);
});
Такой подход позволяет масштабировать систему сообщений без изменения логики тестов.
Для унификации часто используют фабрики сообщений:
const msg = {
required: (field) => `${field} обязательно`,
min: (field, n) => `${field} должно быть не меньше ${n}`,
};
test('password', () => {
enforce(value)
.isNotEmpty()
.message(msg.required('Пароль'));
enforce(value)
.longerThan(8)
.message(msg.min('Пароль', 8));
});
Это уменьшает дублирование и упрощает сопровождение.
Асинхронные тесты могут формировать сообщения после завершения запроса:
test('username', async () => {
const exists = await api.checkUsername(value);
enforce(exists === false)
.message(() => 'Имя пользователя уже занято');
});
Сообщение формируется только после получения результата асинхронной операции, что сохраняет консистентность состояния.
Если несколько проверок одного поля не проходят, Vest сохраняет каждое сообщение отдельно. При отображении можно учитывать порядок выполнения:
test('password', () => {
enforce(value)
.isNotEmpty()
.message('Пароль обязателен');
enforce(value)
.longerThan(8)
.message('Минимальная длина 8 символов');
});
Результат валидации представляет собой структурированный объект, где сообщения связаны с путями полей:
{
errors: {
email: ['Invalid email'],
password: ['Password invalid']
}
}
Такая структура упрощает интеграцию с UI-фреймворками, где сообщения отображаются рядом с соответствующими полями формы.
В сложных сценариях сообщение зависит от комбинации правил:
test('amount', () => {
enforce(value).isNumber();
if (value < 0) {
enforce(false).message('Сумма не может быть отрицательной');
} else if (value > 1000) {
enforce(false).message('Превышен лимит суммы');
}
});
Подобный подход используется при реализации бизнес-ограничений, выходящих за рамки простых валидаторов.
Система сообщений в Vest тесно связана с архитектурой тестов: каждое сообщение является частью декларативного описания проверки, а не внешним слоем отображения. Это позволяет сохранять единый источник истины между логикой валидации и пользовательским интерфейсом, избегая расхождения бизнес-правил и текстового представления ошибок.