Юнит-тестирование схем Yup обеспечивает контроль корректности правил валидации на уровне изолированных модулей, где каждая схема рассматривается как самостоятельная логическая единица. Такой подход снижает риск регрессий при изменении правил валидации и упрощает сопровождение формальных контрактов данных в приложении.
Схемы Yup часто выступают единственным источником истины для структуры входных данных. Они определяют:
Любое изменение в схеме потенциально влияет на поведение интерфейса, API-слоя и бизнес-логики. Юнит-тестирование фиксирует ожидаемое поведение схемы и предотвращает непреднамеренные изменения логики.
Для тестирования схем Yup обычно используются фреймворки Jest или Vitest.
Пример установки зависимостей:
npm install yup
npm install jest
Минимальная конфигурация теста:
import * as yup from 'yup';
Асинхронная природа Yup требует работы с await, так как
метод validate возвращает Promise.
Тестирование схем строится вокруг двух ключевых сценариев:
Базовая структура теста:
test('валидные данные проходят проверку', async () => {
const schema = yup.object({
name: yup.string().required(),
});
const data = { name: 'John' };
const result = await schema.validate(data);
expect(result).toEqual(data);
});
Проверка ошибок:
test('пустое значение вызывает ошибку', async () => {
const schema = yup.object({
name: yup.string().required(),
});
await expect(schema.validate({ name: '' }))
.rejects
.toThrow();
});
const schema = yup.string().min(3).max(10);
Проверка граничных значений:
test('строка короче минимальной длины', async () => {
const schema = yup.string().min(3);
await expect(schema.validate('ab'))
.rejects
.toThrow();
});
test('строка соответствует требованиям длины', async () => {
const schema = yup.string().min(3);
const result = await schema.validate('abcd');
expect(result).toBe('abcd');
});
const schema = yup.number().positive().integer();
Тестирование отрицательных значений:
test('отрицательное число не проходит валидацию', async () => {
const schema = yup.number().positive();
await expect(schema.validate(-5))
.rejects
.toThrow();
});
const schema = yup.object({
email: yup.string().email().required(),
});
test('отсутствие обязательного поля вызывает ошибку', async () => {
const schema = yup.object({
email: yup.string().email().required(),
});
await expect(schema.validate({}))
.rejects
.toThrow();
});
Особое внимание уделяется различию между undefined,
null и пустой строкой, так как Yup обрабатывает их
по-разному.
Объектные схемы требуют проверки вложенной структуры:
const schema = yup.object({
user: yup.object({
name: yup.string().required(),
age: yup.number().min(18),
}),
});
Тест корректного объекта:
test('валидный вложенный объект', async () => {
const data = {
user: {
name: 'Alex',
age: 25,
},
};
const result = await schema.validate(data);
expect(result.user.name).toBe('Alex');
});
Тест частично некорректной структуры:
test('отсутствие вложенного поля вызывает ошибку', async () => {
const data = {
user: {
age: 25,
},
};
await expect(schema.validate(data))
.rejects
.toThrow();
});
const schema = yup.object({
tags: yup.array().of(yup.string().min(2)),
});
Проверка массива:
test('валидный массив строк', async () => {
const data = {
tags: ['js', 'node'],
};
const result = await schema.validate(data);
expect(result.tags.length).toBe(2);
});
Ошибочные элементы массива:
test('элемент массива нарушает правило', async () => {
const data = {
tags: ['j', 'node'],
};
await expect(schema.validate(data))
.rejects
.toThrow();
});
Yup позволяет задавать собственные правила через
.test().
const schema = yup.string().test(
'no-spaces',
'строка не должна содержать пробелы',
value => !/\s/.test(value)
);
Тест кастомной логики:
test('строка с пробелами отклоняется', async () => {
const schema = yup.string().test(
'no-spaces',
'error',
value => !/\s/.test(value)
);
await expect(schema.validate('hello world'))
.rejects
.toThrow();
});
Yup поддерживает преобразование значений через
transform.
const schema = yup.number().transform((value, originalValue) => {
return typeof originalValue === 'string'
? Number(originalValue)
: value;
});
Проверка трансформации:
test('строка преобразуется в число', async () => {
const schema = yup.number().transform((value, originalValue) => {
return Number(originalValue);
});
const result = await schema.validate('42');
expect(result).toBe(42);
});
Асинхронные проверки часто используются для проверки уникальности значений через API или базу данных.
const schema = yup.string().test(
'is-unique',
'значение уже используется',
async (value) => {
const exists = await fakeApiCheck(value);
return !exists;
}
);
Тестирование асинхронной логики требует мокирования:
const fakeApiCheck = jest.fn(async (value) => value === 'taken');
test('асинхронная проверка уникальности', async () => {
const schema = yup.string().test(
'is-unique',
'error',
async (value) => {
const exists = await fakeApiCheck(value);
return !exists;
}
);
await expect(schema.validate('taken'))
.rejects
.toThrow();
});
Для однотипных проверок удобно использовать табличные тесты:
const schema = yup.number().positive();
test.each([
-1,
-10,
0,
])('некорректное значение %s', async (value) => {
await expect(schema.validate(value))
.rejects
.toThrow();
});
Yup поддерживает strict режим, отключающий приведение
типов.
const schema = yup.number().strict();
Тестирование:
test('строковое число не приводится', async () => {
const schema = yup.number().strict();
await expect(schema.validate('123'))
.rejects
.toThrow();
});
При масштабных проектах схемы часто переиспользуются. Для них создаются базовые тестовые наборы:
const emailSchema = yup.string().email().required();
const validCases = [
'test@mail.com',
'user@domain.org',
];
validCases.forEach(value => {
test(`валидный email ${value}`, async () => {
const result = await emailSchema.validate(value);
expect(result).toBe(value);
});
});
Тестирование не ограничивается фактом ошибки, важна и её структура:
test('сообщение ошибки совпадает', async () => {
const schema = yup.string().required('поле обязательно');
try {
await schema.validate('');
} catch (err) {
expect(err.message).toBe('поле обязательно');
}
});
Реальные схемы часто комбинируют множество правил:
const schema = yup.object({
username: yup.string().min(3).required(),
password: yup.string().min(8).required(),
age: yup.number().min(18),
});
Тестирование комбинаций:
test('валидный объект проходит все проверки', async () => {
const data = {
username: 'user123',
password: 'strongpass',
age: 20,
};
const result = await schema.validate(data);
expect(result.username).toBe('user123');
});
Большая часть ценности тестирования схем Yup формируется через негативные кейсы:
Каждый из этих сценариев фиксирует контракт схемы и предотвращает деградацию логики при изменениях.