Какие типы тестов ты писал в Node.js-проектах?

Ответ

В своих 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.

Ключевой практикой считаю изоляцию тестов (каждый тест не должен зависеть от состояния, оставленного другим) и покрытие не только "счастливого пути", но и обработки ошибок и пограничных случаев.

Ответ 18+ 🔞

Давай я тебе расскажу, как я обычно тесты в Node.js проектах пишу, а то тут, бля, целая наука, ёпта. Не просто так, а с умом, чтобы потом не охуеть от багов.

Модульные тесты (Unit) Это когда ты берешь какую-нибудь одну мелкую функцию и начинаешь её мучать со всех сторон, как последнюю мартышлюшку. Типа, вот тебе калькулятор скидок — давай проверяй, не пиздит ли он. Для этого Jest или Mocha с Chai — самое то. А чтобы эта функция не полезла в базу или куда ещё, её зависимости мокаешь. Всё просто.

// 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();
  });
});

Смотри, тут я проверяю: дал сотню премиум-юзеру — должен вернуть девяносто. А если цену отрицательную подсунул — пусть сразу ошибку кидает, а не молча какую-то хуйню считает. Доверия к таким расчётам — ноль ебать.

Интеграционные тесты для API А вот это уже посерьёзнее. Тут нужно проверить, как твои роуты, база данных и внешние сервисы друг с другом дружат. Тут без supertest — никуда. И базу тестовую поднимать надо, чтобы не продовую портить. Я обычно в Docker контейнере её кручу.

// 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');
  });
});

Видишь? Перед каждым тестом таблицу users зачищаю. Чтобы не было такого, что один тест насрал, а второй в этой же куче данные ищет и нихуя не находит. Изоляция, бля, святое дело. Подозрение ебать чувствую, если тесты друг от друга зависят.

Тестирование EventEmitter и потоков А это, чувак, отдельная песня. Когда у тебя сервис на событиях завязан или с потоками (streams) работает. Надо ловить эти события, проверять, что они правильные и в нужный момент летят. А то получится пиздопроебина: данные ушли, а событие 'end' так и не пришло, и всё висит.

Нагрузочное тестирование Это для самых отчаянных. Когда нужно понять, выдержит ли твой красивый асинхронный эндпоинт, когда на него налетит овердохуища запросов. Тут autocannon или artillery в руки — и вперёд. Смотришь, где система начинает бздеть и падать. Очень отрезвляет, когда думаешь, что всё оптимизировал.

Главный принцип, бля, который я усвоил — тесты должны быть изолированными. Каждый тест — как отдельный хуй с горы: сам по себе и ни от кого не зависит. И покрывать надо не только "счастливый путь", когда всё идеально, но и все эти ебанько-сценарии: ошибки, пограничные случаи, невалидные данные. Вот тогда можно спать более-менее спокойно. Ну, или хотя бы не так сильно бздеть перед деплоем.