Моки и заглушки

При разработке прикладной логики на основе библиотек валидации важно отделять чистую проверку данных от внешних зависимостей. Даже при использовании утилитарных решений вроде Validator.js, тестирование часто затрагивает окружение: базу данных, API, файловую систему, сервисы авторизации. Для изоляции поведения применяются тестовые двойники — моки и заглушки.

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

Разделение понятий: mock и stub

Несмотря на частое объединение этих терминов, их поведение различается:

Заглушка (stub) — упрощённая реализация функции или метода, возвращающая заранее определённый результат. Основная цель — обеспечить предсказуемость.

Мок (mock) — расширенная заглушка, которая не только возвращает данные, но и фиксирует факт вызова, аргументы и порядок выполнения.

В контексте Validator.js это особенно важно при тестировании цепочек преобразования данных, где валидатор является частью пайплайна.

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

Validator.js предоставляет набор функций для проверки строковых значений:

import validator from 'validator';

const isValidEmail = (value) => {
  return validator.isEmail(value);
};

При тестировании подобной функции прямой необходимости в моках нет. Однако ситуация меняется, если валидатор встроен в более сложную систему:

const registerUser = async (email, apiClient) => {
  if (!validator.isEmail(email)) {
    throw new Error('Invalid email');
  }

  return apiClient.post('/register', { email });
};

Здесь появляется внешняя зависимость — apiClient, которую необходимо изолировать.

Использование заглушек для внешних сервисов

Заглушка позволяет заменить реальный HTTP-клиент контролируемой реализацией:

const apiClientStub = {
  post: async () => ({ status: 200, data: { success: true } })
};

Тестирование функции регистрации становится детерминированным:

test('registerUser returns success response', async () => {
  const result = await registerUser('test@mail.com', apiClientStub);

  expect(result.data.success).toBe(true);
});

В данном случае заглушка гарантирует отсутствие сетевых запросов и исключает нестабильность тестов.

Моки для проверки поведения

Если требуется не только заменить реализацию, но и проверить факт вызова, используется мок:

const apiClientMock = {
  post: jest.fn().mockResolvedValue({ status: 200 })
};

Проверка взаимодействия:

test('registerUser calls apiClient.post with correct payload', async () => {
  await registerUser('user@mail.com', apiClientMock);

  expect(apiClientMock.post).toHaveBeenCalledWith('/register', {
    email: 'user@mail.com'
  });
});

Мок фиксирует не только результат, но и структуру взаимодействия между компонентами.

Интеграция с Validator.js в тестовых сценариях

Validator.js часто используется как первый слой проверки данных. При тестировании важно учитывать, что библиотека сама по себе не требует мокирования, поскольку является чистой функцией без побочных эффектов. Однако её поведение может быть встроено в более сложные проверки.

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

const processUserInput = (input, sanitizer, api) => {
  if (!validator.isLength(input.email, { min: 5 })) {
    throw new Error('Email too short');
  }

  const cleaned = sanitizer.clean(input);

  return api.save(cleaned);
};

Здесь тестирование требует:

  • заглушки для sanitizer
  • мока для api
  • реального вызова Validator.js

Комбинирование моков и заглушек

Часто используется смешанный подход:

const sanitizerStub = {
  clean: (data) => ({ ...data, sanitized: true })
};

const apiMock = {
  save: jest.fn().mockReturnValue(true)
};

Тест:

test('processUserInput validates and saves data', () => {
  const input = { email: 'test@mail.com' };

  const result = processUserInput(input, sanitizerStub, apiMock);

  expect(apiMock.save).toHaveBeenCalled();
  expect(result).toBe(true);
});

Такое разделение позволяет изолировать ответственность каждого компонента.

Подмена Validator.js в специфических сценариях

Хотя Validator.js редко подменяется напрямую, в некоторых случаях это необходимо, например при тестировании граничных условий или симуляции нестандартного поведения.

jest.mock('validator', () => ({
  isEmail: jest.fn()
}));

Пример управления поведением:

import validator from 'validator';

validator.isEmail.mockReturnValue(false);

Тестирование реакции системы:

test('rejects invalid email using mocked validator', () => {
  const result = () => registerUser('invalid', apiClientStub);

  expect(result).toThrow('Invalid email');
});

Такой подход применяется редко и только при необходимости полного контроля над зависимостью.

Sinon как альтернатива Jest-мокам

При использовании Sinon структура мока выглядит иначе:

import sinon from 'sinon';

const apiClientMock = {
  post: sinon.stub().resolves({ status: 200 })
};

Проверка вызовов:

sinon.assert.calledWith(apiClientMock.post, '/register');

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

Ошибки при использовании моков и заглушек

Распространённые проблемы:

Избыточное мокирование Когда заменяются даже простые чистые функции, такие как Validator.js, тест теряет ценность и перестаёт отражать реальное поведение системы.

Смешение ответственности Использование одного и того же тестового двойника как мока и заглушки одновременно приводит к неясной структуре теста.

Неполное покрытие сценариев Заглушки часто возвращают только «успешный» результат, игнорируя ошибки, что приводит к ложному ощущению стабильности кода.

Принципы использования тестовых двойников

В контексте систем, использующих Validator.js, практическое применение моков и заглушек подчиняется нескольким принципам:

  • внешние зависимости всегда изолируются
  • чистые функции не подменяются без необходимости
  • проверка поведения выполняется через моки
  • стабилизация результата достигается через заглушки
  • Validator.js остаётся частью реальной логики теста

Такая структура тестирования обеспечивает воспроизводимость и прозрачность проверки бизнес-логики, не искажая поведение системы искусственными подменами.