Тестирование в JavaScript: unit, integration, e2e от А до Я

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

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

Начать курс

Тестирование в JavaScript: Unit, Integration, E2E — от А до Я

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

1. Unit-тесты: тестируем изолированные модули

Unit-тесты — это фундамент пирамиды. Они проверяют работу отдельных функций, классов или компонентов в полной изоляции от внешних зависимостей (БД, API, файловой системы). Основная цель — убедиться, что логика модуля работает корректно при любых входных данных.

Для Node.js самым популярным фреймворком является Jest (от Facebook). Он не требует сложной конфигурации, имеет встроенный ассершн, моки и покрытие кода.

Пример: тестируем функцию сортировки массива

// utils.js
function sortNumbers(arr) {
  if (!Array.isArray(arr)) {
    throw new Error('Input must be an array');
  }
  return [...arr].sort((a, b) => a - b);
}

module.exports = { sortNumbers };
// utils.test.js
const { sortNumbers } = require('./utils');

describe('sortNumbers', () => {
  test('should sort an array of numbers in ascending order', () => {
    expect(sortNumbers([3, 1, 2])).toEqual([1, 2, 3]);
  });

  test('should handle an empty array', () => {
    expect(sortNumbers([])).toEqual([]);
  });

  test('should throw error for non-array input', () => {
    expect(() => sortNumbers('string')).toThrow('Input must be an array');
  });
});

Best practices для unit-тестов:

  • Один тест — одна проверка (один assert).
  • Используйте describe для группировки тестов по функциям.
  • Мокайте внешние зависимости (с помощью jest.mock()).
  • Пишите тесты до написания кода (TDD — Test-Driven Development).

2. Интеграционные тесты: проверяем взаимодействие компонентов

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

Для тестирования HTTP-серверов на Express отлично подходит библиотека Supertest в паре с Jest.

Пример: тестируем REST-эндпоинт

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

app.get('/api/hello', (req, res) => {
  res.json({ message: 'Hello, World!' });
});

app.post('/api/data', (req, res) => {
  const { name } = req.body;
  if (!name) {
    return res.status(400).json({ error: 'Name is required' });
  }
  res.status(201).json({ received: name });
});

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

describe('Integration tests for API', () => {
  test('GET /api/hello should return a greeting', async () => {
    const response = await request(app)
      .get('/api/hello')
      .expect(200);

    expect(response.body).toEqual({ message: 'Hello, World!' });
  });

  test('POST /api/data with valid body should return 201', async () => {
    const response = await request(app)
      .post('/api/data')
      .send({ name: 'Alice' })
      .expect(201);

    expect(response.body).toEqual({ received: 'Alice' });
  });

  test('POST /api/data without name should return 400', async () => {
    const response = await request(app)
      .post('/api/data')
      .send({})
      .expect(400);

    expect(response.body).toEqual({ error: 'Name is required' });
  });
});

Советы по интеграционным тестам:

  • Используйте тестовую БД (например, SQLite в памяти или отдельный контейнер Docker).
  • Очищайте данные между тестами (хуки beforeEach / afterEach).
  • Тестируйте не только happy path, но и ошибки (400, 404, 500).

3. E2E-тесты: симуляция действий пользователя

Сквозные (End-to-End) тесты — это вершина пирамиды. Они эмулируют поведение реального пользователя: открывают браузер, кликают по кнопкам, заполняют формы, проверяют, что данные отображаются корректно. E2E-тесты дают наибольшую уверенность, но они самые медленные и хрупкие.

Для Node.js часто используют Puppeteer (управление Chrome) или Playwright (более современный, поддерживает несколько браузеров).

Пример: проверка формы входа

// login.e2e.test.js
const puppeteer = require('puppeteer');

let browser;
let page;

beforeAll(async () => {
  browser = await puppeteer.launch({ headless: true });
  page = await browser.newPage();
});

afterAll(async () => {
  await browser.close();
});

test('should display error for invalid login', async () => {
  await page.goto('http://localhost:3000/login');

  // Заполняем форму
  await page.type('#username', 'wrong_user');
  await page.type('#password', 'wrong_pass');
  await page.click('button[type="submit"]');

  // Ждём появления сообщения об ошибке
  await page.waitForSelector('.error-message', { timeout: 5000 });

  const errorText = await page.$eval('.error-message', el => el.textContent);
  expect(errorText).toContain('Invalid credentials');
});

Когда использовать E2E?

  • Для критических пользовательских сценариев (регистрация, оплата).
  • Для smoke-тестов после деплоя.
  • Когда нужно проверить взаимодействие фронтенда и бэкенда в реальном браузере.

Важно: Не пытайтесь покрыть E2E-тестами всё приложение — это дорого и медленно. Доста

Блоги

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