Ответ
В своих Node.js-проектах я выстраиваю пирамиду тестирования, чтобы балансировать между скоростью выполнения и надежностью.
1. Модульные тесты (Unit) — основа пирамиды.
- Цель: Проверить изолированно отдельную функцию, модуль или класс.
- Инструменты: Jest (основной выбор) или Mocha + Chai.
-
Практика: Мокаю все внешние зависимости (запросы к БД, вызовы API, файловую систему) с помощью
jest.mock()или библиотек вродеsinon.// Тестируем сервисный слой, мокаем модуль работы с БД jest.mock('../db/userRepository'); const { getUserById } = require('../db/userRepository'); const { getUserProfile } = require('../services/userService'); test('getUserProfile returns formatted user data', async () => { // Подготовка мока getUserById.mockResolvedValue({ id: 1, name: 'Alice', email: 'alice@example.com' }); // Вызов тестируемой функции const result = await getUserProfile(1); // Проверки expect(result).toHaveProperty('displayName', 'Alice'); expect(getUserById).toHaveBeenCalledWith(1); });
2. Интеграционные тесты.
- Цель: Проверить взаимодействие нескольких модулей (например, Express-роут + сервис + репозиторий).
- Инструменты: Jest, Supertest для тестирования HTTP API.
-
Практика: Поднимаю тестовую БД (часто в Docker-контейнере или используя
sqlite:memory:для SQL) и очищаю её перед каждым тестом.const request = require('supertest'); const app = require('../app'); // Express app const db = require('../db'); beforeAll(async () => await db.migrate.latest()); afterEach(async () => await db('users').truncate()); test('POST /api/users creates a new user', async () => { const res = await request(app) .post('/api/users') .send({ name: 'Bob' }); expect(res.statusCode).toBe(201); expect(res.body).toHaveProperty('id'); // Проверяем, что данные реально попали в БД const dbUser = await db('users').where({ id: res.body.id }).first(); expect(dbUser.name).toBe('Bob'); });
3. E2E (сквозные) тесты.
- Цель: Имитировать поведение реального пользователя в максимально приближенной к продакшену среде.
- Инструменты: Playwright (мой фаворит) или Cypress.
- Практика: Запускаю всё приложение (frontend + backend) в тестовом окружении и автоматизирую сценарии через браузер.
// Пример с Playwright test('user can complete checkout flow', async ({ page }) => { await page.goto('/products/1'); await page.click('button:has-text("Add to Cart")'); await page.goto('/cart'); await expect(page.locator('.cart-item')).toHaveCount(1); await page.click('button:has-text("Checkout")'); // ... заполнение формы, оплата await expect(page).toHaveURL('/order/confirmed'); });
Дополнительные виды:
- Нагрузочные тесты: Использую k6 для проверки, как API держит нагрузку в 100-1000 RPS.
- Тесты на утечки памяти: Пишу скрипты, которые создают нагрузку, и слежу за памятью процесса Node.js с помощью
--inspectи Chrome DevTools.
Стратегия: Пишу много быстрых модульных тестов, покрывающих бизнес-логику. Интеграционные тесты покрывают критические API-маршруты. Несколько ключевых E2E-тестов проверяют основные пользовательские сценарии. Это дает уверенность при рефакторинге и непрерывной поставке.