В современных фронтенд-приложениях валидация форм часто строится
вокруг связки схемной валидации и менеджера состояния формы. Одним из
распространённых решений становится использование Yup как
декларативного описания правил и resolver, который
связывает схему с системой управления формой.
YupResolver выполняет роль адаптера между схемой
валидации и механизмом обработки формы. Он преобразует результат
проверки Yup в формат, понятный библиотекам управления
формами, прежде всего в контексте интеграции с React Hook Form.
Ключевая задача резолвера заключается в унификации процесса валидации:
Yup реализует декларативный подход, при котором правила
описываются как схема данных. Это позволяет отделить бизнес-логику
проверки от компонентов интерфейса.
Пример базовой схемы:
import * as Yup from "yup";
const schema = Yup.object({
email: Yup.string()
.email("Некорректный формат email")
.required("Email обязателен"),
password: Yup.string()
.min(8, "Минимум 8 символов")
.required("Пароль обязателен"),
});
Схема становится единым источником истины для валидации. Она может переиспользоваться как в клиентской части, так и в тестах.
Важной особенностью Yup является поддержка:
testwhenИнтеграция схемы валидации осуществляется через резолвер:
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
const form = useForm({
resolver: yupResolver(schema),
mode: "onChange",
});
Механизм работы можно описать следующим образом:
Результатом становится унифицированный объект ошибок, где ключи соответствуют именам полей.
Результат работы резолвера имеет предсказуемую структуру:
{
values: {},
errors: {
email: {
type: "validation",
message: "Некорректный формат email"
}
}
}
Такая структура позволяет UI-слою не зависеть от логики валидации. Интерфейс оперирует только состоянием формы и текстами ошибок.
В сложных формах с вложенными структурами (например, адреса, списки товаров) ошибки могут быть вложенными:
errors: {
user: {
address: {
city: {
message: "Город обязателен"
}
}
}
}
Асинхронные проверки часто используются для:
Yup поддерживает асинхронные тесты:
Yup.string().test(
"check-email",
"Email уже используется",
async (value) => {
const res = await fetch(`/api/check-email?email=${value}`);
const data = await res.json();
return data.available;
}
);
При использовании YupResolver асинхронная валидация становится частью общего потока обработки формы. Это важно учитывать при E2E тестировании, так как поведение формы становится зависимым от сетевых задержек и состояния API.
E2E тестирование форм направлено на проверку поведения интерфейса в условиях, максимально приближенных к реальному использованию.
Проверяются следующие аспекты:
Основной фокус переносится с изолированных функций на полный пользовательский сценарий.
E2E тестирование обычно реализуется через инструменты вроде Cypress или Playwright. В основе лежит автоматизация браузера и управление DOM-событиями.
Пример структуры теста:
test("форма регистрации", async ({ page }) => {
await page.goto("/register");
await page.fill("input[name=email]", "test@test.com");
await page.fill("input[name=password]", "123");
await page.click("button[type=submit]");
});
При использовании YupResolver важно учитывать задержку валидации, так как ошибки могут появляться асинхронно.
Классический сценарий заключается в отправке пустой формы:
await page.click("button[type=submit]");
await expect(page.locator("text=Email обязателен")).toBeVisible();
await expect(page.locator("text=Пароль обязателен")).toBeVisible();
Такой тест проверяет связку UI → React Hook Form → YupResolver.
await page.fill("input[name=email]", "invalid-email");
await page.blur("input[name=email]");
await expect(page.locator("text=Некорректный формат email")).toBeVisible();
Blur-событие важно, так как многие формы используют режим
onBlur или onChange.
await page.fill("input[name=password]", "123");
await page.blur("input[name=password]");
await expect(page.locator("text=Минимум 8 символов")).toBeVisible();
Асинхронная валидация требует ожиданий, связанных с завершением сетевых запросов.
При тестировании уникальности email часто используется перехват запросов:
await page.route("**/api/check-email*", (route) =>
route.fulfill({
status: 200,
body: JSON.stringify({ available: false }),
})
);
После этого выполняется ввод:
await page.fill("input[name=email]", "test@test.com");
await page.blur("input[name=email]");
await expect(page.locator("text=Email уже используется")).toBeVisible();
Одной из сложностей E2E тестирования становится асинхронность обновления состояния формы.
YupResolver может выполнять проверку:
Это влияет на стратегию ожиданий:
await expect(page.locator("text=Ошибка")).toBeVisible({
timeout: 5000,
});
Использование явных ожиданий предпочтительнее фиксированных задержек.
Вложенные структуры данных требуют проверки глубинных ошибок:
await page.fill("input[name='user.address.city']", "");
await page.click("button[type=submit]");
await expect(page.locator("text=Город обязателен")).toBeVisible();
Такие сценарии часто используются в формах профиля, заказов и анкет.
При E2E тестировании форм критично использовать стабильные селекторы:
data-testiddata-cydata-e2eПример:
await page.fill("[data-testid=email-input]", "test@test.com");
Это снижает зависимость тестов от структуры DOM и CSS.
Финальный этап сценария связан с отправкой формы:
await page.fill("input[name=email]", "test@test.com");
await page.fill("input[name=password]", "12345678");
await page.click("button[type=submit]");
await expect(page).toHaveURL("/dashboard");
При использовании YupResolver отправка формы блокируется при наличии ошибок, что также проверяется:
await page.click("button[type=submit]");
await expect(page).toHaveURL("/register");
Использование YupResolver влияет на структуру E2E тестов, так как формальная логика валидации становится централизованной.
Это приводит к следующим характеристикам тестовой модели:
Схема Yup может использоваться не только в UI, но и в тестовой среде для подготовки эталонных данных, что повышает согласованность между приложением и тестами.
Часто встречающиеся проблемы:
onChangeЭти факторы напрямую влияют на стабильность тестов, особенно при использовании схемной валидации через YupResolver.
В некоторых реализациях формы используется задержка перед валидацией:
setTimeout(() => validate(), 300);
Это требует увеличения таймаутов ожидания в тестах или использования clock-mocking:
await page.clock.install();
await page.clock.fastForward(500);
Комплексные формы включают:
Пример массива:
await page.click("button#add-item");
await page.fill("input[name='items[0].name']", "");
await page.click("button[type=submit]");
YupResolver корректно агрегирует ошибки по массивам, что проверяется через соответствие структуры DOM.
Валидация через Yup часто отражает бизнес-правила:
E2E тестирование фиксирует соответствие этих правил реальному поведению интерфейса, обеспечивая непротиворечивость между слоем UI и доменной логикой.