NestJS E2E Testing
End-to-end (E2E) testing validates the entire application flow — from HTTP request through controllers, services, and database, all the way to the response. Unlike unit tests that test one piece in isolation, E2E tests confirm that all pieces work together correctly as a complete system. They catch integration bugs that unit tests miss.
E2E vs Unit Testing
Unit Test:
UserService.findOne(1) → mock repository → { id: 1, name: 'Alice' }
Tests: service logic only, no HTTP, no database
E2E Test:
GET /users/1 → Controller → Service → Real (or test) DB → HTTP 200
Tests: full request pipeline including routing, guards, pipes, serialization
The Test Folder
NestJS CLI generates an E2E test folder automatically:
test/ app.e2e-spec.ts ← E2E test file jest-e2e.json ← Jest config for E2E tests
Setting Up an E2E Test
// test/users.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('UsersController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
// Apply the same global config as your real app
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.init();
});
afterAll(async () => {
await app.close();
});
describe('POST /users', () => {
it('creates a user and returns 201', async () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'Alice', email: 'alice@test.com', age: 30 })
.expect(201)
.expect(res => {
expect(res.body.name).toBe('Alice');
expect(res.body.email).toBe('alice@test.com');
expect(res.body.id).toBeDefined();
});
});
it('returns 400 when email is invalid', async () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'Bob', email: 'not-an-email', age: 25 })
.expect(400);
});
});
describe('GET /users/:id', () => {
it('returns the user', async () => {
return request(app.getHttpServer())
.get('/users/1')
.expect(200)
.expect(res => {
expect(res.body.id).toBe(1);
});
});
it('returns 404 for non-existent user', async () => {
return request(app.getHttpServer())
.get('/users/99999')
.expect(404);
});
});
});
Supertest Request Methods
request(app.getHttpServer())
.get('/path') // GET request
.post('/path') // POST request
.put('/path') // PUT request
.patch('/path') // PATCH request
.delete('/path') // DELETE request
.set('Authorization', 'Bearer token') // add header
.send({ key: 'value' }) // set request body
.expect(200) // assert status code
.expect(res => { ... }) // assert response body
Testing Authenticated Routes
describe('GET /users/profile (authenticated)', () => {
let accessToken: string;
beforeAll(async () => {
// Get a token by logging in first
const loginRes = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: 'alice@test.com', password: 'password123' });
accessToken = loginRes.body.access_token;
});
it('returns profile when authenticated', async () => {
return request(app.getHttpServer())
.get('/users/profile')
.set('Authorization', `Bearer ${accessToken}`)
.expect(200)
.expect(res => {
expect(res.body.email).toBe('alice@test.com');
});
});
it('returns 401 without a token', async () => {
return request(app.getHttpServer())
.get('/users/profile')
.expect(401);
});
});
Using a Test Database
Running E2E tests against your development database mutates real data. Use a separate test database to keep tests isolated:
// .env.test
DB_NAME=myapp_test
NODE_ENV=test
// jest-e2e.json
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": { "^.+\\.(t|j)s$": "ts-jest" },
"setupFiles": ["dotenv/config"] // loads .env.test
}
Running E2E Tests
npm run test:e2e
E2E Test Scope Diagram
E2E Test:
POST /users { name, email, age }
|
v
ValidationPipe (validates input)
|
v
UsersController.create() (routing)
|
v
UsersService.create() (business logic)
|
v
UserRepository.save() (database write)
|
v
HTTP 201 { id, name, email, createdAt }
Every layer runs. A bug in any layer fails the test.
E2E tests are slower than unit tests because they boot the full application and connect to a database. Run unit tests frequently during development and E2E tests before commits or deployments. Together, unit tests and E2E tests give you confidence at both the individual component level and the full system level.
