Механизм test в Yup представляет собой основной
инструмент расширения стандартной системы валидации. Он позволяет
задавать произвольные правила проверки, выходящие за рамки встроенных
методов (required, min, max,
email и др.), и интегрировать бизнес-логику непосредственно
в схему.
В контексте использования YupResolver (например, в
связке с react-hook-form) такие правила становятся частью
декларативной схемы, которая затем преобразуется резолвером в результат
валидации формы.
.test()Метод test добавляется к любому полю схемы Yup и
принимает конфигурационный объект или набор аргументов:
Yup.string().test(
name,
message,
testFunction
)
Где:
true/false или
ValidationErrorПростейший пример:
import * as Yup from "yup";
const schema = Yup.object({
username: Yup.string()
.test(
"no-spaces",
"Имя пользователя не должно содержать пробелы",
(value) => !/\s/.test(value || "")
)
});
Функция testFunction получает доступ к значению поля и
дополнительному контексту через this (в обычной функции, не
стрелочной):
Yup.string().test(
"starts-with-a",
"Значение должно начинаться с буквы A",
function (value) {
return value?.startsWith("A");
}
);
Контекст this содержит:
this.parent — объект текущей формыthis.path — путь к полюthis.originalValue — исходное значениеthis.options — параметры схемыИспользование this.parent позволяет реализовывать
межполевую валидацию:
Yup.object({
password: Yup.string().required(),
confirmPassword: Yup.string().test(
"match-password",
"Пароли не совпадают",
function (value) {
return value === this.parent.password;
}
)
});
test поддерживает асинхронную валидацию, что особенно
важно при проверке уникальности данных через API.
const schema = Yup.object({
email: Yup.string()
.email()
.test(
"check-email-exists",
"Email уже используется",
async (value) => {
if (!value) return true;
const response = await fetch(`/api/check-email?email=${value}`);
const data = await response.json();
return data.available === true;
}
)
});
Особенность интеграции с YupResolver заключается в том,
что резолвер корректно обрабатывает Promise и переводит результат в
структуру ошибок формы.
contextПри интеграции с YupResolver можно передавать внешний
контекст в схему через context, что позволяет создавать
динамические правила.
import { yupResolver } from "@hookform/resolvers/yup";
const schema = Yup.object({
age: Yup.number().test(
"min-age-by-country",
"Возраст не соответствует требованиям страны",
function (value) {
const country = this.options.context?.country;
if (country === "US") return value >= 21;
if (country === "EU") return value >= 18;
return true;
}
)
});
Использование:
resolver: yupResolver(schema, {
context: { country: "US" }
});
test часто применяется для сложной логики, где
стандартные методы Yup недостаточны.
const schema = Yup.object({
isCompany: Yup.boolean(),
companyName: Yup.string().test(
"company-required",
"Название компании обязательно",
function (value) {
const isCompany = this.parent.isCompany;
if (!isCompany) return true;
return typeof value === "string" && value.trim().length > 0;
}
)
});
Здесь поле становится обязательным только при определённом условии.
Для масштабируемых схем часто требуется вынести тест в отдельную функцию, чтобы использовать его в разных местах.
const noSpecialChars = (message) =>
function (value) {
return /^[a-zA-Z0-9]*$/.test(value || "");
};
const schema = Yup.object({
code: Yup.string().test(
"no-special-chars",
"Код содержит недопустимые символы",
noSpecialChars()
)
});
Более гибкий вариант с параметрами:
const minWords = (count) =>
function (value) {
if (!value) return true;
return value.trim().split(/\s+/).length >= count;
};
const schema = Yup.object({
description: Yup.string().test(
"min-words",
"Недостаточно слов",
minWords(10)
)
});
createErrorВнутри test можно не только возвращать
true/false, но и формировать детализированные ошибки:
Yup.string().test(
"complex-validation",
"Ошибка",
function (value) {
if (!value) {
return this.createError({ message: "Поле обязательно" });
}
if (value.length < 5) {
return this.createError({
message: "Минимальная длина 5 символов"
});
}
return true;
}
);
Такой подход полезен, когда требуется несколько уровней проверки в одном тесте.
YupResolver преобразует Yup-схему в формат ошибок,
понятный react-hook-form. При использовании
test важно учитывать, что:
Пример подключения:
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
const form = useForm({
resolver: yupResolver(schema)
});
test в одном полеНесколько тестов могут применяться последовательно:
Yup.string()
.test("not-empty", "Пустое значение", (v) => !!v)
.test("no-digits", "Не должно содержать цифры", (v) => !/\d/.test(v || ""))
.test("min-length", "Минимум 3 символа", (v) => (v || "").length >= 3);
Каждый тест выполняется независимо, и первый неуспешный прерывает цепочку.
Часто встречающиеся проблемы:
thisundefined или
nulltrue/falsetestПример некорректного подхода:
Yup.string().test("bad", "Ошибка", (value) => {
this.parent; // недоступно
return value.length > 3;
});
Правильный вариант:
Yup.string().test("good", "Ошибка", function (value) {
return (value || "").length > 3;
});
test может использоваться не только для проверки, но и
для подготовки данных через побочные механизмы схемы (хотя напрямую
трансформация выполняется через transform).
Yup.string().test(
"trim-check",
"Некорректное значение",
function (value) {
const trimmed = value?.trim();
if (!trimmed) return this.createError({ message: "Пустое значение" });
return true;
}
);
При работе с вложенными объектами test получает доступ к
структуре на любом уровне:
const schema = Yup.object({
user: Yup.object({
profile: Yup.object({
age: Yup.number().test(
"adult-check",
"Возраст должен быть 18+",
function (value) {
return value >= 18;
}
)
})
})
});
this.path в этом случае будет содержать полный путь:
user.profile.age, что позволяет точно локализовать
ошибки.
При увеличении количества test важно учитывать
производительность:
Пример кеширования:
let cache = new Map();
Yup.string().test(
"cached-check",
"Ошибка проверки",
async (value) => {
if (cache.has(value)) return cache.get(value);
const result = await fetch(`/api/check?q=${value}`)
.then((r) => r.json());
cache.set(value, result.ok);
return result.ok;
}
);
test и схемных методов Yuptest не заменяет стандартные методы, а дополняет их.
Обычно архитектура схемы строится так:
required, min,
max)testYup.string()
.required()
.min(5)
.test("custom-rule", "Ошибка бизнес-логики", function (value) {
return value !== "admin";
});