Тестирование в JavaScript: Unit, Integration и E2E тесты с Node.js

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Тестирование в JavaScript: Unit, Integration и E2E тесты

Тестирование — это неотъемлемая часть разработки любого качественного программного обеспечения. В мире JavaScript (особенно в экосистеме Node.js) существует три основных уровня тестирования: unit-тесты (модульные), интеграционные тесты и E2E тесты (сквозные). Каждый из них решает свои задачи и используется на разных этапах разработки. В этой статье мы разберём каждый тип, покажем примеры кода и дадим практические рекомендации.

1. Unit-тесты (модульное тестирование)

Unit-тесты проверяют отдельные, изолированные единицы кода — функции, методы или классы. Главная цель — убедиться, что каждая «единица» работает корректно независимо от остальной системы. В Node.js для этого чаще всего используют фреймворки Jest или Mocha с Chai.

Пример unit-теста на Jest

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

// discount.js
function calculateDiscount(price, percent) {
  if (typeof price !== 'number' || typeof percent !== 'number') {
    throw new Error('Оба аргумента должны быть числами');
  }
  if (price < 0 || percent < 0 || percent > 100) {
    throw new Error('Некорректные значения');
  }
  return price - (price * percent / 100);
}

module.exports = calculateDiscount;

Тест для этой функции:

// discount.test.js
const calculateDiscount = require('./discount');

describe('calculateDiscount', () => {
  test('корректно вычисляет 10% скидку от 1000', () => {
    expect(calculateDiscount(1000, 10)).toBe(900);
  });

  test('возвращает 0 при 100% скидке', () => {
    expect(calculateDiscount(500, 100)).toBe(0);
  });

  test('выбрасывает ошибку при отрицательной цене', () => {
    expect(() => calculateDiscount(-100, 10)).toThrow('Некорректные значения');
  });

  test('выбрасывает ошибку при нечисловых аргументах', () => {
    expect(() => calculateDiscount('100', 10)).toThrow('Оба аргумента должны быть числами');
  });
});
  • Плюсы: Быстрые, легко писать, точно указывают на проблемный модуль.
  • Минусы: Не проверяют взаимодействие между компонентами.
  • Когда использовать: Для функций бизнес-логики, утилит, валидаторов.

2. Интеграционные тесты (Integration tests)

Интеграционные тесты проверяют, как различные модули или сервисы работают вместе. Например, взаимодействие с базой данных, внешним API или файловой системой. В Node.js для таких тестов часто используют Supertest (для HTTP-запросов) и Jest.

Пример интеграционного теста для Express-приложения

Предположим, у нас есть простой Express-сервер с эндпоинтом для создания пользователя:

// server.js
const express = require('express');
const app = express();
app.use(express.json());

const users = [];

app.post('/users', (req, res) => {
  const { name, email } = req.body;
  if (!name || !email) {
    return res.status(400).json({ error: 'Name and email are required' });
  }
  const user = { id: users.length + 1, name, email };
  users.push(user);
  res.status(201).json(user);
});

module.exports = app;

Интеграционный тест с Supertest:

// server.test.js
const request = require('supertest');
const app = require('./server');

describe('POST /users', () => {
  test('создаёт нового пользователя с валидными данными', async () => {
    const response = await request(app)
      .post('/users')
      .send({ name: 'Иван', email: 'ivan@example.com' });

    expect(response.statusCode).toBe(201);
    expect(response.body).toHaveProperty('id');
    expect(response.body.name).toBe('Иван');
  });

  test('возвращает 400, если не указан email', async () => {
    const response = await request(app)
      .post('/users')
      .send({ name: 'Иван' });

    expect(response.statusCode).toBe(400);
    expect(response.body.error).toBe('Name and email are required');
  });
});
  • Плюсы: Проверяют реальное взаимодействие компонентов, ловят ошибки на стыках.
  • Минусы: Медленнее unit-тестов, требуют настройки окружения (БД, API).
  • Когда использовать: Для тестирования API-эндпоинтов, работы с БД, взаимодействия с внешними сервисами.

3. E2E тесты (сквозное тестирование)

E2E тесты имитируют действия реального пользователя: от открытия браузера до выполнения полного сценария. Они проверяют всю систему целиком — фронтенд, бэкенд, базу данных. Для JavaScript-проектов популярны Cypress, Playwright и Puppeteer.

Пример E2E теста с Cypress

Предположим, у нас есть форма регистрации на сайте. Cypress-тест будет выглядеть так:

// cypress/e2e/register.cy.js
describe('Регистрация пользователя', () => {
  it('успешно регистрирует нового пользователя', () => {
    cy.visit('/register');

    cy.get('input[name="name"]').type('Анна');
    cy.get('input[name="email"]').type('anna@test.com');
    cy.get('input[name="password"]').type('securePass123');
    cy.get('button[type="submit"]').click();

    // Проверяем, что произошёл редирект на страницу приветствия
    cy.url().should('include', '/welcome');
    cy.contains('Добро пожаловать, Анна!').should('be.visible');
  });
});
  • Плюсы: Максимально приближены к реальному пользовательскому опыту, проверяют всю цепочку.
  • Минусы: Самые медленные, хрупкие (зависят от UI), сложны в поддержке.
  • Когда использовать: Для критических пользовательских сценариев (регистрация, оплата, авторизация).

4. Практи

Блоги

Рекомендаций по книгам