Ответ
В своих Node.js-проектах я применяю многоуровневое тестирование, обычно с использованием Jest или комбинации Mocha/Chai/Sinon.
1. Модульные (Unit) тесты: Тестирую изолированные функции, утилиты или классы. Для изоляции зависимостей использую моки (Jest или Sinon).
// tests/utils/calculator.test.js
const { calculateDiscount } = require('../../utils/calculator');
describe('calculateDiscount', () => {
it('should apply 10% discount for premium users', () => {
const result = calculateDiscount(100, 'premium');
expect(result).toBe(90);
});
it('should throw error for negative price', () => {
expect(() => calculateDiscount(-50, 'standard')).toThrow();
});
});
2. Интеграционные тесты API:
Тестирую взаимодействие с базой данных и внешними сервисами. Использую supertest для HTTP-запросов и тестовую базу данных (например, Docker-контейнер с PostgreSQL).
// tests/integration/user.api.test.js
const request = require('supertest');
const app = require('../../app');
const db = require('../../db');
beforeEach(async () => {
await db.query('TRUNCATE TABLE users CASCADE'); // Очистка перед каждым тестом
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@test.com' });
expect(res.statusCode).toBe(201);
expect(res.body).toHaveProperty('id');
});
});
3. Тестирование событий EventEmitter и потоков: Для сервисов, работающих с потоками (streams) или кастомными EventEmitter, пишу тесты, проверяющие корректность генерируемых событий и обработку данных.
4. Нагрузочное тестирование:
Для критичных эндпоинтов использовал autocannon или artillery для проверки поведения системы под нагрузкой, что особенно важно для асинхронных операций в Node.js.
Ключевой практикой считаю изоляцию тестов (каждый тест не должен зависеть от состояния, оставленного другим) и покрытие не только "счастливого пути", но и обработки ошибок и пограничных случаев.