Когда приложение маленькое, можно запустить его руками и посмотреть, работает ли. Когда оно растёт — ручная проверка перестаёт успевать за изменениями. Нужны автоматические тесты, которые запускаются за секунды и сразу показывают, что сломалось.
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 — как подключить базу к тестам.