Интеграционное тестирование

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

Основная задача Validator.js в подобных сценариях — обеспечивать единообразную проверку входных данных на разных уровнях приложения. Библиотека предоставляет набор функций для проверки строк, чисел, форматов и структур данных, например isEmail, isLength, isInt, isURL, а также методы нормализации данных. При интеграционном тестировании важно учитывать не только корректность самих функций, но и то, как они встроены в бизнес-логику.

В реальных приложениях Validator.js редко используется в одиночку. Чаще он включается в состав серверных обработчиков или клиентских форм. Например, в Node.js-среде он может быть частью middleware слоя:

import validator from "validator";

export function validateUserInput(req, res, next) {
  const { email, password } = req.body;

  const errors = [];

  if (!validator.isEmail(email || "")) {
    errors.push("Некорректный email");
  }

  if (!validator.isLength(password || "", { min: 8 })) {
    errors.push("Пароль слишком короткий");
  }

  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }

  next();
}

Интеграционное тестирование в этом случае проверяет не только функции Validator.js, но и корректность HTTP-ответов, статус-кодов и поведения middleware-цепочки.

Проверка API через Supertest

Для тестирования серверной интеграции часто используется связка Jest и Supertest. Это позволяет имитировать реальные HTTP-запросы и проверять ответ приложения целиком.

import request from "supertest";
import app from "../app";

describe("POST /register", () => {
  it("должен вернуть ошибку при некорректном email", async () => {
    const response = await request(app)
      .post("/register")
      .send({
        email: "invalid-email",
        password: "strongpassword123"
      });

    expect(response.status).toBe(400);
    expect(response.body.errors).toContain("Некорректный email");
  });
});

Здесь Validator.js работает внутри серверного слоя, а тест оценивает уже итоговое поведение системы.

Интеграция с клиентскими формами

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

Пример логики формы:

import validator from "validator";

export function validateForm(data) {
  const errors = {};

  if (!validator.isEmail(data.email || "")) {
    errors.email = "Email введён неверно";
  }

  if (!validator.isLength(data.password || "", { min: 8 })) {
    errors.password = "Минимальная длина пароля — 8 символов";
  }

  return errors;
}

Интеграционный тест может проверять связку «ввод → валидация → отображение ошибки»:

import { validateForm } from "../validateForm";

test("форма возвращает ошибки при некорректных данных", () => {
  const errors = validateForm({
    email: "bad-email",
    password: "123"
  });

  expect(errors.email).toBeDefined();
  expect(errors.password).toBeDefined();
});

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

Сценарии с внешними зависимостями

Интеграционные тесты становятся особенно важными, когда валидация зависит от внешних источников данных. Например, проверка уникальности email в базе данных:

import validator from "validator";
import User from "../models/User";

export async function validateRegistration(data) {
  const errors = [];

  if (!validator.isEmail(data.email || "")) {
    errors.push("Некорректный email");
  }

  const exists = await User.findOne({ email: data.email });

  if (exists) {
    errors.push("Пользователь уже существует");
  }

  return errors;
}

Тестирование такого сценария требует подготовки тестовой базы:

import { validateRegistration } from "../validateRegistration";
import User from "../models/User";

beforeEach(async () => {
  await User.deleteMany({});
  await User.create({ email: "test@mail.com" });
});

test("возвращает ошибку при существующем пользователе", async () => {
  const errors = await validateRegistration({
    email: "test@mail.com"
  });

  expect(errors).toContain("Пользователь уже существует");
});

Поведение при множественных ошибках

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

test("возвращает все ошибки сразу", () => {
  const errors = validateForm({
    email: "wrong",
    password: "1"
  });

  expect(Object.keys(errors).length).toBe(2);
});

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

Проверка нормализации данных

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

import validator from "validator";

export function sanitizeUser(data) {
  return {
    email: validator.normalizeEmail(data.email || ""),
    username: validator.trim(data.username || "")
  };
}

Тестирование:

test("нормализует email и username", () => {
  const result = sanitizeUser({
    email: " TEST@MAIL.COM ",
    username: "  user  "
  });

  expect(result.email).toBe("test@mail.com");
  expect(result.username).toBe("user");
});

Комбинирование правил валидации

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

function isStrongPassword(password) {
  return (
    validator.isLength(password, { min: 8 }) &&
    /[A-Z]/.test(password) &&
    /[0-9]/.test(password)
  );
}

Тесты должны учитывать разные комбинации входных данных:

test("пароль считается слабым без цифр", () => {
  expect(isStrongPassword("Password")).toBe(false);
});

test("пароль считается сильным при выполнении всех условий", () => {
  expect(isStrongPassword("Password1")).toBe(true);
});

Ошибки интеграции и типовые проблемы

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

  • отсутствие обработки undefined или null значений;
  • несогласованность сообщений об ошибках между слоями;
  • различия между клиентской и серверной валидацией;
  • неправильный порядок применения правил;
  • дублирование проверок на разных уровнях системы.

Эти проблемы особенно заметны при тестировании полного цикла запроса, когда данные проходят через несколько слоёв обработки.

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

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

async function validateEmailUniqueness(email) {
  if (!validator.isEmail(email)) {
    return "Некорректный email";
  }

  const exists = await User.findOne({ email });

  return exists ? "Email уже используется" : null;
}

Тестирование требует ожидания результата:

test("обнаруживает занятый email", async () => {
  const message = await validateEmailUniqueness("taken@mail.com");
  expect(message).toBe("Email уже используется");
});