Метод test для кастомной валидации

Общая концепция кастомной проверки

Валидация данных в Yup строится вокруг декларативных схем, однако стандартных правил (таких как required, min, max, email) недостаточно для сложных бизнес-логик. Для таких случаев используется метод test, позволяющий внедрять произвольную функцию проверки значения.

Метод test применяется ко всем типам схем: строкам, числам, массивам, объектам и т.д., обеспечивая единый механизм расширения встроенной валидации.


Сигнатура метода test

Базовая форма вызова:

schema.test(name, message, testFunction)

Параметры:

  • name — уникальное имя теста (строка)
  • message — сообщение об ошибке при провале проверки
  • testFunction — функция, выполняющая проверку значения

Поведение функции проверки

Функция testFunction получает значение поля и контекст выполнения. Она может возвращать:

  • true — значение валидно
  • false — значение не проходит проверку
  • ValidationError — кастомная ошибка
  • Promise — для асинхронной валидации

Простейшая форма:

const schema = yup.string().test(
  'starts-with-a',
  'Строка должна начинаться с буквы a',
  (value) => {
    return value?.startsWith('a');
  }
);

Контекст выполнения теста

Функция проверки вызывается с доступом к контексту this, содержащему полезные данные:

  • this.value — текущее значение
  • this.originalValue — исходное значение до преобразований
  • this.path — путь к полю в объекте
  • this.schema — текущая схема
  • this.options — параметры валидации
  • this.parent — родительский объект (для вложенных схем)

Пример использования контекста:

const schema = yup.object({
  password: yup.string(),
  confirmPassword: yup.string().test(
    'match-password',
    'Пароли не совпадают',
    function (value) {
      return value === this.parent.password;
    }
  )
});

Использование createError

Для более гибкого управления ошибками применяется this.createError.

const schema = yup.number().test(
  'positive-even',
  'Число должно быть положительным и чётным',
  function (value) {
    if (value == null) return true;

    if (value <= 0) {
      return this.createError({ message: 'Число должно быть больше нуля' });
    }

    if (value % 2 !== 0) {
      return this.createError({ message: 'Число должно быть чётным' });
    }

    return true;
  }
);

Такой подход позволяет возвращать разные ошибки в зависимости от причины провала проверки.


Асинхронная валидация

Метод test поддерживает асинхронные операции, что особенно важно при проверке уникальности значений через API или базу данных.

const schema = yup.string().test(
  'is-unique-username',
  'Имя пользователя уже занято',
  async function (value) {
    if (!value) return true;

    const response = await fetch(`/api/check-username?name=${value}`);
    const result = await response.json();

    return result.available;
  }
);

Асинхронные тесты автоматически обрабатываются Yup при вызове validate.


Множественные тесты

На одну схему можно навешивать несколько test, каждый из которых выполняется последовательно.

const schema = yup.string()
  .test('no-spaces', 'Нельзя использовать пробелы', value => !value.includes(' '))
  .test('min-length', 'Минимум 5 символов', value => value.length >= 5);

Порядок выполнения влияет на итоговую ошибку: первая неудачная проверка прерывает цепочку.


Доступ к параметрам через context

Yup позволяет передавать внешний контекст при валидации, который доступен внутри test.

const schema = yup.number().test(
  'max-by-role',
  'Превышено допустимое значение',
  function (value) {
    const { role } = this.options.context;

    const limits = {
      admin: 1000,
      user: 100
    };

    return value <= limits[role];
  }
);

В этом случае логика проверки становится зависимой от внешних условий.


Использование параметров теста

Метод test может принимать дополнительные параметры через объектную форму:

yup.string().test({
  name: 'custom-length',
  message: 'Недопустимая длина',
  test: function (value) {
    return value.length >= 3 && value.length <= 10;
  }
});

Такая форма удобна при большом количестве конфигураций и расширенной метаинформации.


Поведение при undefined и null

По умолчанию test не обязан обрабатывать null и undefined. Их поведение определяется другими методами схемы (required, nullable).

Частая практика — явная проверка:

(value) => {
  if (value == null) return true;
  return value.startsWith('#');
}

Взаимодействие с трансформациями

Если схема содержит transform, значение в test поступает уже после преобразования.

const schema = yup.number()
  .transform((val, originalVal) => Number(originalVal))
  .test('is-integer', 'Должно быть целым', value => Number.isInteger(value));

Повторное использование логики проверки

Для переиспользуемых правил часто выносится функция:

const isEven = (value) => value % 2 === 0;

const schema = yup.number().test(
  'even-number',
  'Число должно быть чётным',
  isEven
);

Такой подход упрощает масштабирование валидации в больших схемах.


Ошибки и приоритет тестов

При наличии нескольких ошибок в рамках одного поля Yup возвращает первую найденную ошибку в порядке выполнения тестов. Это влияет на стратегию построения цепочек проверок: более критичные условия располагаются раньше.

yup.string()
  .test('required-format', 'Неверный формат', value => !!value)
  .test('length-check', 'Слишком короткое значение', value => value.length > 3);

Особенности работы внутри объектов и массивов

При использовании в структурах данных this.path позволяет определить точное местоположение значения:

yup.object({
  users: yup.array().of(
    yup.object({
      age: yup.number().test(
        'adult-check',
        'Возраст должен быть не менее 18',
        function (value) {
          console.log(this.path); // users[0].age
          return value >= 18;
        }
      )
    })
  )
});

Ограничения и типичные ошибки использования

  • Возврат undefined вместо true или false может привести к некорректной интерпретации результата
  • Асинхронные тесты без await в вызывающем коде могут не отработать корректно
  • Использование this в стрелочных функциях приводит к потере контекста
  • Избыточное количество тестов ухудшает читаемость схемы и усложняет отладку

Итоговые особенности метода test

Метод test формирует основу расширяемости Yup, обеспечивая возможность внедрения любой логики проверки, включая синхронные и асинхронные сценарии, доступ к контексту формы, кастомные ошибки и интеграцию с внешними источниками данных.