Сквозные тесты с supertest
Сквозной (end-to-end, e2e) тест проверяет приложение так, как его видит клиент: отправляет настоящие HTTP-запросы и смотрит на коды и тела ответов. В отличие от модульного теста, здесь работают все части сразу — пайпы, гарды, сервисы и база. Проект NestJS уже содержит такой тест и библиотеку supertest для отправки запросов.
Где лежат тесты
Сквозные тесты живут в папке test/ в корне проекта, файлы называются *.e2e-spec.ts. Пример — test/app.e2e-spec.ts. Запускаются они отдельной командой:
npm run test:e2eОтдельная база для тестов
Тесты создают и удаляют данные, поэтому им нужна своя база. SQLite умеет хранить базу в памяти: если путь равен :memory:, база создаётся при запуске и исчезает после. Переменные окружения важнее файла .env, поэтому достаточно задать DB_PATH в начале теста.
Тест статей
Создайте файл test/posts.e2e-spec.ts:
// test/posts.e2e-spec.ts
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { AppModule } from '../src/app.module';
process.env.DB_PATH = ':memory:';
describe('Posts (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.init();
});
afterAll(async () => {
await app.close();
});
it('GET /posts возвращает пустой список', () => {
return request(app.getHttpServer()).get('/posts').expect(200).expect([]);
});
it('GET /posts/999 возвращает 404', () => {
return request(app.getHttpServer()).get('/posts/999').expect(404);
});
});createNestApplication() создаёт приложение из всего AppModule, но без запуска на порту: supertest сам передаёт запросы серверу через app.getHttpServer(). Глобальные настройки из src/main.ts в тест не попадают, поэтому ValidationPipe подключён здесь заново.
Запросы с телом и токеном
// test/posts.e2e-spec.ts
it('POST /posts без токена возвращает 401', () => {
return request(app.getHttpServer())
.post('/posts')
.send({ title: 'Тест', content: 'Текст' })
.expect(401);
});
it('вход и создание статьи', async () => {
await request(app.getHttpServer())
.post('/users')
.send({ email: 'anna@example.com', name: 'Анна', password: 'password123' })
.expect(201);
const login = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: 'anna@example.com', password: 'password123' })
.expect(200);
const response = await request(app.getHttpServer())
.post('/posts')
.set('Authorization', `Bearer ${login.body.accessToken}`)
.send({ title: 'Тест', content: 'Текст' })
.expect(201);
expect(response.body.id).toBeDefined();
});send() отправляет JSON и сам ставит заголовок Content-Type, set() добавляет заголовок, а response.body содержит разобранный ответ. При успехе в терминале появится Tests: 4 passed, 4 total.
Частая ошибка
Тесты запускаются на рабочей базе blog.sqlite и портят или удаляют настоящие данные. Всегда задавайте отдельную базу для тестов до создания модуля: строка process.env.DB_PATH = ':memory:' должна стоять выше Test.createTestingModule(). Для PostgreSQL заведите отдельную тестовую базу и указывайте её в переменных окружения.