Коротко — что обычно используют и почему
- Jest + Supertest — стандарт для e2e в NestJS: Jest как раннер/ассерты, Supertest для HTTP-запросов к запущенному приложению.
- Запуск реального Nest-приложения (Test.createTestingModule -> module.createNestApplication()) с app.init() — чтобы тесты проходили через все middleware, guards и pipes.
- ValidationPipe глобально в тестовом приложении — чтобы проверка DTO работала так же, как в проде.
- Тестовая БД: либо отдельная тестовая база (Postgres/MySQL), либо in-memory (SQLite), либо контейнеры (testcontainers). Для Prisma/TypeORM — очищайте БД между тестами (deleteMany / repository.clear / транзакции).
- Моки для внешних сервисов (email, queue, SSO) — заменяйте реальные провайдеры заглушками через overrideProvider.
- Не держите state между тестами: чистите БД в beforeEach/afterEach или создавайте транзакции + rollback.
Конкретный пример e2e‑теста для POST /auth/register (Jest + Supertest). Предположим, что:
- У вас AppModule,
- DTO для регистрации использует class-validator (@IsEmail, @IsString, @MinLength и т.д.),
- Вы используете TypeORM (далее также покажу Prisma-паттерн).
Пример для TypeORM
tests/auth.e2e-spec.ts
(схема — минимальна, вставьте путь к AppModule и сущности/репозиторию вашего проекта)
```ts
import request from 'supertest';
import { Test } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { getConnection, Repository } from 'typeorm';
import { AppModule } from '../src/app.module';
import { User } from '../src/users/user.entity';
import { getRepositoryToken } from '@nestjs/typeorm';
describe('AuthController (e2e)', () => {
let app: INestApplication;
let userRepo: Repository<User>;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
// Включаем валидацию как в проде
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
await app.init();
userRepo = moduleRef.get<Repository<User>>(getRepositoryToken(User));
});
afterAll(async () => {
await getConnection().close();
await app.close();
});
beforeEach(async () => {
// чистим таблицу пользователей между тестами
await userRepo.query(`TRUNCATE TABLE "user" RESTART IDENTITY CASCADE`);
// либо: await userRepo.clear();
});
it('POST /auth/register - success', async () => {
const payload = {
email: `user${Date.now()}@example.com`,
password: 'strongPassword123',
name: 'User Name',
};
const res = await request(app.getHttpServer())
.post('/auth/register')
.send(payload)
.expect(201);
// базовые проверки ответа
expect(res.body).toHaveProperty('id');
expect(res.body.email).toBe(payload.email);
expect(res.body).not.toHaveProperty('password'); // пароль не должен возвращаться
});
it('POST /auth/register - invalid email -> 400', async () => {
const payload = {
email: 'not-an-email',
password: 'strongPassword123',
name: 'User Name',
};
const res = await request(app.getHttpServer())
.post('/auth/register')
.send(payload)
.expect(400);
// Проверяем формат ошибки, может отличаться в вашем проекте
expect(res.body).toHaveProperty('statusCode', 400);
expect(res.body).toHaveProperty('message');
// Обычно message содержит массив строк ошибок валидации
expect(res.body.message.some((m: string) => m.toLowerCase().includes('email'))).toBeTruthy();
});
});
```
Примечания к коду:
- ValidationPipe в тесте — критично, иначе некорректный email не даст 400.
- Проверки в expect(...) подстраивайте под формат ошибок вашего приложения (Nest стандартно возвращает {statusCode, message, error}).
- TRUNCATE/clear нужен, чтобы тесты были изолированы. В PostgreSQL удобно TRUNCATE ... CASCADE. Для SQLite/Prisma — другие команды.
Если вы используете Prisma
- В beforeAll получите PrismaService (или client) и между тестами делайте await prisma.user.deleteMany() для очистки.
- В AppModule для e2e можно overrideProvider(EmailService).useValue(mockMail) чтобы не шлём реальные письма.
Пример очистки для Prisma:
```ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ...
beforeEach(async () => {
await prisma.user.deleteMany();
});
afterAll(async () => {
await prisma.$disconnect();
});
```
Советы и распространённые ошибки
- Убедитесь, что DTO использует @IsEmail(), иначе валидация не сработает.
- Если ваш pipeline использует transform: true (ValidationPipe), в тестах можно включить transform, если нужен типизированный body.
- Мокайте внешние ресурсы (email, redis, внешние API) — e2e лучше проверяет HTTP-поток и интеграцию с БД, но внешние сервисы должны быть под контролем.
- Используйте отдельную БД/контейнер для тестов, чтобы не портить dev/production данные.
- Не забывайте про таймауты Jest для медленных интеграционных тестов (jest.setTimeout или configuration).
Если хотите — пришлите ваш текущий код теста и AppModule (или DTO/Controller) — помогу адаптировать и показать точный исправленный пример под ваш проект.