Тестирование в 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), сложны в поддержке.
- Когда использовать: Для критических пользовательских сценариев (регистрация, оплата, авторизация).