Ответ
В своих 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-тестов проверяют основные пользовательские сценарии. Это дает уверенность при рефакторинге и непрерывной поставке.
Ответ 18+ 🔞
А, ну вот, тема-то какая, ебать мои старые костыли! Слушай, про пирамиду тестирования в Node.js. Это ж как раз про то, как не сойти с ума и не ждать час, пока твои тесты прогуляются по всем кустам. Я тебе сейчас на пальцах, но с кодом, объясню, как у меня это устроено.
1. Модульные тесты (Unit) — это фундамент, ёпта.
- Зачем: Чтобы проверить одну маленькую функцию, как она сама по себе работает. Без всяких приключений с базой данных или интернетами.
- Чем бью: В основном Jest, потому что он из коробки всё умеет. Ну или Mocha с Chai, если ты любитель старых добрых сборок.
-
Как делаю: Всё, что не сама функция — нахер мокаю. Базу, API, файлы — всё в труху.
jest.mock()мой лучший друг в этом деле.// Тестируем сервисный слой, мокаем модуль работы с БД 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. Интеграционные тесты — где начинается магия, а иногда и пиздец.
- Зачем: Убедиться, что твой роут, сервис и репозиторий не поссорились и работают вместе, как одна семья (ну или хотя бы не дерутся).
- Чем бью: Тот же Jest, но уже с Supertest, чтобы по API стучать.
-
Как делаю: Поднимаю тестовую базу, обычно в Docker, чтобы она была одна и никому не мешала. И после каждого теста её вычищаю, чтобы тесты друг другу не пакостили.
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 тоже норм, но я за первый.
- Как делаю: Поднимаю ВСЁ приложение (фронт и бэк) в тестовом окружении и заставляю бездушный браузер тыкать в кнопки.
// Пример с 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, как будто там черная пятница. Смотрю, не накроется ли он медным тазом при тысяче запросов в секунду.
- Тесты на утечки памяти: Пишу скрипт, который создаёт бардак, и смотрю в Chrome DevTools, не превращается ли мой процесс Node.js в смерда пердящего, который жрёт память без остановки.
Итоговая стратегия, чтобы не охуеть: Пишу дохуя быстрых модульных тестов на всю логику. Добавляю интеграционные на самые важные маршруты — чтобы быть уверенным, что части между собой дружат. И гордо венчаю эту пирамиду парой-тройкой ключевых E2E-сценариев, которые проверяют, что пользователь в принципе может купить у меня этот долбаный товар. Так и живу.