При тестировании кода, использующего схему валидации через Yup и резолвер (например, в связке с формами), ключевая задача заключается в изоляции поведения валидатора от внешних факторов. YupResolver выступает промежуточным слоем между схемой и формой, преобразуя результат валидации в формат, удобный для потребления UI-слоем. Именно этот слой чаще всего требует применения моков и стабов для предсказуемого поведения тестов.
Валидационные схемы Yup часто содержат сложную бизнес-логику: условные проверки, асинхронные запросы, кастомные тесты. В тестах компонента или резолвера подобная логика становится источником нестабильности, поэтому схема подменяется мок-объектом.
Типичная стратегия заключается в замене реальной схемы на упрощённую реализацию:
const mockSchema = {
validate: jest.fn(),
};
В контексте YupResolver важно, что схема должна иметь метод
validate. При этом поведение метода полностью
контролируется тестом:
mockSchema.validate.mockResolvedValue({
name: "valid",
});
или в случае ошибки:
mockSchema.validate.mockRejectedValue({
inner: [
{
path: "name",
message: "Обязательное поле",
},
],
});
Такой подход позволяет тестировать только логику резолвера без влияния реальной Yup-валидации.
YupResolver сам по себе является функцией-обёрткой, возвращающей асинхронный резолвер. В тестовой среде часто требуется стабировать результат его работы, чтобы исключить зависимость от внутренней реализации Yup.
import { yupResolver } from "@hookform/resolvers/yup";
jest.mock("@hookform/resolvers/yup", () => ({
yupResolver: jest.fn(),
}));
После этого поведение резолвера задаётся явно:
yupResolver.mockReturnValue(async (values) => {
if (values.name) {
return {
values,
errors: {},
};
}
return {
values: {},
errors: {
name: {
type: "required",
message: "Обязательное поле",
},
},
};
});
Такой стаб позволяет проверять реакцию формы на результат резолвера без выполнения реальной схемы.
При наличии сложной схемы, содержащей трансформации и кастомные
проверки, мокирование может включать эмуляцию поведения
cast, validateSync и isValid.
const mockSchema = {
validate: jest.fn(),
validateSync: jest.fn(),
isValid: jest.fn(),
cast: jest.fn(),
};
Пример поведения:
mockSchema.isValid.mockResolvedValue(true);
mockSchema.validateSync.mockReturnValue({
email: "test@mail.com",
});
Подобная структура позволяет моделировать различные сценарии:
Yup возвращает ошибки в виде объекта ValidationError,
содержащего массив inner. При тестировании резолвера этот
формат часто имитируется вручную.
const validationError = {
inner: [
{
path: "email",
message: "Некорректный email",
},
{
path: "password",
message: "Слишком короткий пароль",
},
],
};
Затем стабируется отклонение промиса:
mockSchema.validate.mockRejectedValue(validationError);
Резолвер в таком случае должен трансформировать структуру в формат ошибок формы:
{
email: { message: "Некорректный email" },
password: { message: "Слишком короткий пароль" }
}
В некоторых сценариях требуется тестировать не сам YupResolver, а слой над ним. Тогда резолвер полностью стабируется как функция:
const resolverMock = jest.fn();
Поведение задаётся напрямую:
resolverMock.mockImplementation(async (data) => {
if (data.email === "ok@mail.com") {
return {
values: data,
errors: {},
};
}
return {
values: {},
errors: {
email: {
message: "Ошибка",
},
},
};
});
Такой подход позволяет полностью исключить Yup и сосредоточиться на взаимодействии формы и результата резолвера.
Асинхронные проверки (например, уникальность email) требуют отдельного моделирования. Вместо реальных запросов используются стаб-функции:
const checkEmail = jest.fn();
Сценарии:
checkEmail.mockResolvedValue(true);
checkEmail.mockResolvedValue(false);
или имитация ошибки сети:
checkEmail.mockRejectedValue(new Error("Network error"));
В схеме Yup это обычно выглядит как:
Yup.string().test(
"check-email",
"Email уже существует",
async (value) => {
return await checkEmail(value);
}
);
Наиболее изолированный подход достигается при одновременной подмене схемы и резолвера. Схема возвращает фиксированный результат, а резолвер преобразует его в формат формы.
mockSchema.validate.mockResolvedValue({
username: "user",
});
yupResolver.mockReturnValue(async () => ({
values: { username: "user" },
errors: {},
}));
Такое разделение позволяет тестировать отдельные уровни ответственности без пересечений логики.
Синхронная ошибка схемы может возникать при использовании
validateSync. В тестах она моделируется через выброс
исключения:
mockSchema.validateSync.mockImplementation(() => {
throw {
inner: [
{
path: "age",
message: "Некорректный возраст",
},
],
};
});
Резолвер в таком случае обязан обработать исключение и преобразовать его в структуру ошибок формы.
При использовании Jest возможно автоматическое мокирование модуля Yup:
jest.mock("yup", () => ({
object: jest.fn(),
string: jest.fn(),
number: jest.fn(),
}));
Такой подход используется при необходимости исключить всю цепочку валидации и заменить её предсказуемыми заглушками.
Уровень мокирования определяется задачей теста:
validate — проверка обработки ошибок
резолверомГранулярность влияет на стабильность тестов и их устойчивость к изменениям в библиотеке Yup или реализации резолвера.