← назад к разделу

Когда приложение маленькое, можно запустить его руками и посмотреть, работает ли. Когда оно растёт — ручная проверка перестаёт успевать за изменениями. Нужны автоматические тесты, которые запускаются за секунды и сразу показывают, что сломалось.

NestJS тестируется удобно, потому что всё построено на внедрении зависимостей: любой провайдер можно заменить на тестовый, не трогая код, который его использует. Тестовый раннер — Jest; он поставляется вместе с NestJS.

Три уровня тестирования

Тесты делят на три уровня — их называют пирамидой:

  • Нижний уровень (unit-тесты) — быстрые, их много. Проверяют один класс в изоляции: реальных баз нет, зависимости заменены на моки.
  • Средний уровень (интеграционные тесты) — поднимают несколько связанных частей, иногда реальную базу. Медленнее, но проверяют, что части работают вместе.
  • Верхний уровень (e2e-тесты) — шлют настоящие HTTP-запросы ко всему приложению. Самые медленные, их держат немного.

NestJS даёт инструменты для всех трёх уровней.

Тестовый модуль

Для unit- и интеграционных тестов NestJS предоставляет Test.createTestingModule — это облегчённый контейнер, который собирается как обычный модуль, но только с теми провайдерами, что нужны для теста.

Типичный unit-тест сервиса:

import { Test } from '@nestjs/testing';

describe('CreateProductHandler', () => {
  let handler: CreateProductHandler;
  const repo = { save: jest.fn() };

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [
        CreateProductHandler,
        { provide: ProductRepository, useValue: repo },
      ],
    }).compile();

    handler = moduleRef.get(CreateProductHandler);
  });

  it('сохраняет продукт и возвращает его id', async () => {
    repo.save.mockResolvedValue({ id: 1 });
    const result = await handler.handle({ name: 'Кофемолка', price: 4990 });
    expect(result.id).toBe(1);
  });
});

Здесь CreateProductHandler получает мок вместо настоящего репозитория. Базы нет — тест запускается мгновенно. Jest автоматически сбрасывает моки между тестами через beforeEach.

Замена провайдеров в реальном модуле

Иногда удобно поднять полноценный модуль, но подменить только одну зависимость. Для этого есть overrideProvider:

const moduleRef = await Test.createTestingModule({
  imports: [ProductModule],
})
  .overrideProvider(ProductRepository)
  .useValue(fakeRepository)
  .compile();

Так же работает overrideGuard — чтобы тестировать контроллер без настоящей авторизации:

  .overrideGuard(JwtAuthGuard)
  .useValue({ canActivate: () => true })
  .compile();

Подменяемость встроена в NestJS, потому что всё идёт через DI-контейнер.

E2E-тесты через supertest

Сквозной тест поднимает всё приложение и шлёт ему настоящие HTTP-запросы. Для этого используют библиотеку supertest:

import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';

describe('Products (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it('POST /products возвращает 201', () => {
    return request(app.getHttpServer())
      .post('/products')
      .send({ name: 'Кофемолка', price: 4990 })
      .expect(201);
  });

  afterAll(async () => {
    await app.close();
  });
});

E2E-тест проходит весь конвейер: pipes, guards, контроллер, сервис. Это самый полный способ проверить контракт API, но и самый медленный — поэтому таких тестов держат немного.

Тесты с реальной базой данных

E2E-тесты с данными бьют в реальную базу того же типа, что в продакшене: PostgreSQL — в PostgreSQL, а не в SQLite. Тестовая база должна быть отдельной и воспроизводимой.

Два распространённых подхода:

  • Поднять базу в контейнере перед прогоном тестов (Docker или Testcontainers). Миграции накатываются автоматически, после прогона контейнер удаляется.
  • Обернуть каждый тест в транзакцию с откатом — тест делает запросы в базу, в конце транзакция откатывается, данные не остаются.

Оба подхода гарантируют, что тесты не зависят от порядка запуска и не мешают друг другу.

Коротко

  • NestJS использует Jest как тестовый раннер — он поставляется в стандартной поставке.
  • Test.createTestingModule собирает облегчённый контейнер с нужными провайдерами — основа unit-тестов.
  • Зависимости заменяют через useValue (в описании провайдера) или overrideProvider (поверх реального модуля).
  • overrideGuard позволяет отключить авторизацию в тестах контроллеров.
  • supertest + createNestApplication — стандартный способ e2e-тестирования.
  • Тесты с реальной базой подключают базу того же типа, что в продакшене, — через контейнер или откат транзакций.
  • Пирамида: много быстрых unit-тестов снизу, немного медленных e2e сверху.

Что почитать дальше

  • Модули и DI в NestJS — как устроен контейнер зависимостей.
  • Guards и авторизация — что именно заменяет overrideGuard.
  • Persistence: TypeORM — как подключить базу к тестам.