E2e_testing
Chapter 43: E2E Testing
Section titled “Chapter 43: E2E Testing”End-to-End Testing with TypeORM and NestJS
Section titled “End-to-End Testing with TypeORM and NestJS”43.1 E2E Testing Overview
Section titled “43.1 E2E Testing Overview”End-to-end tests verify the entire application stack from HTTP request to database response.
E2E Testing Flow ================================================================================
HTTP Request | v +------------------+ | Test Client | <-- supertest +------------------+ | v +------------------+ | NestJS App | <-- Full application +------------------+ | v +------------------+ | Service | +------------------+ | v +------------------+ | Database | <-- Test database +------------------+
What E2E Tests Verify: - HTTP endpoints - Request/response format - Authentication - Authorization - Full request flow
================================================================================43.2 E2E Test Setup
Section titled “43.2 E2E Test Setup”Test Application Factory
Section titled “Test Application Factory”import { NestFactory } from '@nestjs/core';import { AppModule } from '../src/app.module';import { ValidationPipe } from '@nestjs/common';import { DataSource } from 'typeorm';
export class TestAppFactory { private static app: any; private static dataSource: DataSource;
static async create() { const module = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(DataSource) .useFactory({ factory: async () => { this.dataSource = new DataSource({ type: 'postgres', host: 'localhost', port: 5432, username: 'postgres', password: 'postgres', database: 'myapp_e2e_test', entities: ['src/**/*.entity{.ts,.js}'], synchronize: true, dropSchema: true, }); return this.dataSource.initialize(); }, }) .compile();
this.app = module.createNestApplication();
// Apply same configuration as production this.app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), );
await this.app.init(); return this.app; }
static async cleanup() { if (this.dataSource?.isInitialized) { await this.dataSource.destroy(); } if (this.app) { await this.app.close(); } }
static async clearDatabase() { if (this.dataSource?.isInitialized) { const entities = this.dataSource.entityMetadatas; for (const entity of entities) { const repository = this.dataSource.getRepository(entity.name); await repository.query(`TRUNCATE "${entity.tableName}" CASCADE`); } } }}Basic E2E Test
Section titled “Basic E2E Test”import { Test, TestingModule } from '@nestjs/testing';import { INestApplication } from '@nestjs/common';import * as request from 'supertest';import { AppModule } from '../src/app.module';import { TestDatabaseService } from './test-database.service';
describe('Users (e2e)', () => { let app: INestApplication; let accessToken: string;
beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }) .overrideProvider(DataSource) .useFactory({ factory: async () => { return TestDatabaseService.getDataSource(); }, }) .compile();
app = moduleFixture.createNestApplication(); await app.init(); });
afterAll(async () => { await app.close(); await TestDatabaseService.cleanup(); });
beforeEach(async () => { await TestDatabaseService.clearTables(); });
describe('/users (POST)', () => { it('should create a user', () => { return request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', }) .expect(201) .expect((res) => { expect(res.body).toMatchObject({ name: 'John Doe', email: 'john@example.com', }); expect(res.body.id).toBeDefined(); expect(res.body.password).toBeUndefined(); }); });
it('should reject invalid email', () => { return request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'invalid-email', password: 'password123', }) .expect(400); });
it('should reject short password', () => { return request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com', password: 'short', }) .expect(400); }); });
describe('/users (GET)', () => { it('should return empty array initially', () => { return request(app.getHttpServer()) .get('/users') .expect(200) .expect([]); });
it('should return created users', async () => { // Create user first await request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', });
return request(app.getHttpServer()) .get('/users') .expect(200) .expect((res) => { expect(res.body).toHaveLength(1); expect(res.body[0].name).toBe('John Doe'); }); }); });
describe('/users/:id (GET)', () => { it('should return a user by id', async () => { const createRes = await request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', });
const userId = createRes.body.id;
return request(app.getHttpServer()) .get(`/users/${userId}`) .expect(200) .expect((res) => { expect(res.body.name).toBe('John Doe'); }); });
it('should return 404 for non-existent user', () => { return request(app.getHttpServer()) .get('/users/999') .expect(404); }); });});43.3 Testing Authentication
Section titled “43.3 Testing Authentication”Auth E2E Test
Section titled “Auth E2E Test”import { Test, TestingModule } from '@nestjs/testing';import { INestApplication } from '@nestjs/common';import * as request from 'supertest';import { AppModule } from '../src/app.module';import { TestDatabaseService } from './test-database.service';import * as bcrypt from 'bcrypt';
describe('Authentication (e2e)', () => { let app: INestApplication; let accessToken: string; let refreshToken: string;
beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();
app = moduleFixture.createNestApplication(); await app.init(); });
afterAll(async () => { await app.close(); });
beforeEach(async () => { await TestDatabaseService.clearTables(); });
describe('/auth/register (POST)', () => { it('should register a new user', () => { return request(app.getHttpServer()) .post('/auth/register') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', }) .expect(201) .expect((res) => { expect(res.body).toMatchObject({ user: { name: 'John Doe', email: 'john@example.com', }, accessToken: expect.any(String), refreshToken: expect.any(String), }); }); });
it('should reject duplicate email', async () => { await request(app.getHttpServer()) .post('/auth/register') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', });
return request(app.getHttpServer()) .post('/auth/register') .send({ name: 'Jane Doe', email: 'john@example.com', password: 'password456', }) .expect(409); }); });
describe('/auth/login (POST)', () => { beforeEach(async () => { await request(app.getHttpServer()) .post('/auth/register') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', }); });
it('should login with correct credentials', () => { return request(app.getHttpServer()) .post('/auth/login') .send({ email: 'john@example.com', password: 'password123', }) .expect(200) .expect((res) => { expect(res.body.accessToken).toBeDefined(); expect(res.body.refreshToken).toBeDefined(); accessToken = res.body.accessToken; refreshToken = res.body.refreshToken; }); });
it('should reject wrong password', () => { return request(app.getHttpServer()) .post('/auth/login') .send({ email: 'john@example.com', password: 'wrongpassword', }) .expect(401); });
it('should reject non-existent user', () => { return request(app.getHttpServer()) .post('/auth/login') .send({ email: 'nonexistent@example.com', password: 'password123', }) .expect(401); }); });
describe('/auth/refresh (POST)', () => { beforeEach(async () => { const res = await request(app.getHttpServer()) .post('/auth/register') .send({ name: 'John Doe', email: 'john@example.com', password: 'password123', }); refreshToken = res.body.refreshToken; });
it('should refresh tokens', () => { return request(app.getHttpServer()) .post('/auth/refresh') .send({ refreshToken }) .expect(200) .expect((res) => { expect(res.body.accessToken).toBeDefined(); expect(res.body.refreshToken).toBeDefined(); }); });
it('should reject invalid refresh token', () => { return request(app.getHttpServer()) .post('/auth/refresh') .send({ refreshToken: 'invalid-token' }) .expect(401); }); });
describe('Protected Routes', () => { beforeEach(async () => { const res = await request(app.getHttpServer()) .post('/auth/login') .send({ email: 'john@example.com', password: 'password123', }); accessToken = res.body.accessToken; });
it('should access protected route with token', () => { return request(app.getHttpServer()) .get('/users/profile') .set('Authorization', `Bearer ${accessToken}`) .expect(200); });
it('should reject without token', () => { return request(app.getHttpServer()) .get('/users/profile') .expect(401); });
it('should reject invalid token', () => { return request(app.getHttpServer()) .get('/users/profile') .set('Authorization', 'Bearer invalid-token') .expect(401); }); });});43.4 Testing File Uploads
Section titled “43.4 Testing File Uploads”File Upload E2E Test
Section titled “File Upload E2E Test”import { Test, TestingModule } from '@nestjs/testing';import { INestApplication } from '@nestjs/common';import * as request from 'supertest';import { AppModule } from '../src/app.module';import * as path from 'path';import * as fs from 'fs';
describe('File Upload (e2e)', () => { let app: INestApplication; let accessToken: string;
beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();
app = moduleFixture.createNestApplication(); await app.init(); });
afterAll(async () => { await app.close(); });
describe('/users/:id/avatar (POST)', () => { it('should upload avatar', async () => { const testImagePath = path.join(__dirname, 'fixtures', 'test-avatar.png');
// Create test image if not exists if (!fs.existsSync(testImagePath)) { fs.mkdirSync(path.dirname(testImagePath), { recursive: true }); fs.writeFileSync(testImagePath, Buffer.from([0x89, 0x50, 0x4E, 0x47])); }
return request(app.getHttpServer()) .post('/users/1/avatar') .set('Authorization', `Bearer ${accessToken}`) .attach('file', testImagePath) .expect(201) .expect((res) => { expect(res.body.avatarUrl).toBeDefined(); }); });
it('should reject non-image file', () => { const testFilePath = path.join(__dirname, 'fixtures', 'test.txt'); fs.writeFileSync(testFilePath, 'test content');
return request(app.getHttpServer()) .post('/users/1/avatar') .set('Authorization', `Bearer ${accessToken}`) .attach('file', testFilePath) .expect(400); });
it('should reject large file', () => { // Create a file larger than limit const largeFile = path.join(__dirname, 'fixtures', 'large.bin'); const buffer = Buffer.alloc(10 * 1024 * 1024); // 10MB fs.writeFileSync(largeFile, buffer);
return request(app.getHttpServer()) .post('/users/1/avatar') .set('Authorization', `Bearer ${accessToken}`) .attach('file', largeFile) .expect(400); }); });});43.5 Testing Pagination and Filtering
Section titled “43.5 Testing Pagination and Filtering”Query E2E Test
Section titled “Query E2E Test”import { Test, TestingModule } from '@nestjs/testing';import { INestApplication } from '@nestjs/common';import * as request from 'supertest';import { AppModule } from '../src/app.module';import { TestDatabaseService } from './test-database.service';import { UserFixture } from './fixtures/user.fixture';
describe('Users Query (e2e)', () => { let app: INestApplication;
beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();
app = moduleFixture.createNestApplication(); await app.init(); });
afterAll(async () => { await app.close(); });
beforeEach(async () => { await TestDatabaseService.clearTables(); });
describe('Pagination', () => { beforeEach(async () => { // Create 25 users const users = UserFixture.createMany(25); await request(app.getHttpServer()) .post('/users/bulk') .send(users); });
it('should return first page', () => { return request(app.getHttpServer()) .get('/users?page=1&limit=10') .expect(200) .expect((res) => { expect(res.body.data).toHaveLength(10); expect(res.body.meta.total).toBe(25); expect(res.body.meta.page).toBe(1); expect(res.body.meta.totalPages).toBe(3); }); });
it('should return second page', () => { return request(app.getHttpServer()) .get('/users?page=2&limit=10') .expect(200) .expect((res) => { expect(res.body.data).toHaveLength(10); expect(res.body.meta.page).toBe(2); }); });
it('should return last page', () => { return request(app.getHttpServer()) .get('/users?page=3&limit=10') .expect(200) .expect((res) => { expect(res.body.data).toHaveLength(5); }); });
it('should handle invalid page', () => { return request(app.getHttpServer()) .get('/users?page=999&limit=10') .expect(200) .expect((res) => { expect(res.body.data).toHaveLength(0); }); }); });
describe('Filtering', () => { beforeEach(async () => { await request(app.getHttpServer()) .post('/users') .send({ name: 'John Doe', email: 'john@example.com', isActive: true }); await request(app.getHttpServer()) .post('/users') .send({ name: 'Jane Doe', email: 'jane@example.com', isActive: false }); await request(app.getHttpServer()) .post('/users') .send({ name: 'Bob Smith', email: 'bob@example.com', isActive: true }); });
it('should filter by active status', () => { return request(app.getHttpServer()) .get('/users?isActive=true') .expect(200) .expect((res) => { expect(res.body.data).toHaveLength(2); res.body.data.forEach((user: any) => { expect(user.isActive).toBe(true); }); }); });
it('should search by name', () => { return request(app.getHttpServer()) .get('/users?search=Doe') .expect(200) .expect((res) => { expect(res.body.data).toHaveLength(2); res.body.data.forEach((user: any) => { expect(user.name).toContain('Doe'); }); }); });
it('should combine filters', () => { return request(app.getHttpServer()) .get('/users?isActive=true&search=John') .expect(200) .expect((res) => { expect(res.body.data).toHaveLength(1); expect(res.body.data[0].name).toBe('John Doe'); }); }); });
describe('Sorting', () => { beforeEach(async () => { await request(app.getHttpServer()) .post('/users') .send({ name: 'Zack', email: 'zack@example.com' }); await request(app.getHttpServer()) .post('/users') .send({ name: 'Alice', email: 'alice@example.com' }); await request(app.getHttpServer()) .post('/users') .send({ name: 'Bob', email: 'bob@example.com' }); });
it('should sort by name ascending', () => { return request(app.getHttpServer()) .get('/users?sortBy=name&order=asc') .expect(200) .expect((res) => { expect(res.body.data[0].name).toBe('Alice'); expect(res.body.data[1].name).toBe('Bob'); expect(res.body.data[2].name).toBe('Zack'); }); });
it('should sort by name descending', () => { return request(app.getHttpServer()) .get('/users?sortBy=name&order=desc') .expect(200) .expect((res) => { expect(res.body.data[0].name).toBe('Zack'); expect(res.body.data[1].name).toBe('Bob'); expect(res.body.data[2].name).toBe('Alice'); }); }); });});43.6 Test Helpers
Section titled “43.6 Test Helpers”Authentication Helper
Section titled “Authentication Helper”import * as request from 'supertest';
export class AuthHelper { constructor(private app: any) {}
async register(userData: any): Promise<{ accessToken: string; userId: number }> { const res = await request(this.app.getHttpServer()) .post('/auth/register') .send(userData);
return { accessToken: res.body.accessToken, userId: res.body.user.id, }; }
async login(email: string, password: string): Promise<string> { const res = await request(this.app.getHttpServer()) .post('/auth/login') .send({ email, password });
return res.body.accessToken; }
authenticatedRequest(accessToken: string) { return { get: (url: string) => request(this.app.getHttpServer()) .get(url) .set('Authorization', `Bearer ${accessToken}`), post: (url: string) => request(this.app.getHttpServer()) .post(url) .set('Authorization', `Bearer ${accessToken}`), put: (url: string) => request(this.app.getHttpServer()) .put(url) .set('Authorization', `Bearer ${accessToken}`), delete: (url: string) => request(this.app.getHttpServer()) .delete(url) .set('Authorization', `Bearer ${accessToken}`), }; }}
// Usagedescribe('With Auth Helper', () => { let authHelper: AuthHelper; let accessToken: string;
beforeEach(async () => { authHelper = new AuthHelper(app); const result = await authHelper.register({ name: 'Test User', email: 'test@example.com', password: 'password123', }); accessToken = result.accessToken; });
it('should access protected route', async () => { const { body } = await authHelper .authenticatedRequest(accessToken) .get('/users/profile') .expect(200);
expect(body.email).toBe('test@example.com'); });});43.7 Summary
Section titled “43.7 Summary” E2E Testing Quick Reference +------------------------------------------------------------------+ | | | Test Type | Purpose | | -------------------|------------------------------------------| | Endpoint tests | Test HTTP endpoints | | Auth tests | Test authentication flow | | Upload tests | Test file uploads | | Query tests | Test pagination, filtering, sorting | | | | Tools | Description | | -------------------|------------------------------------------| | supertest | HTTP request testing | | Test factories | Create test applications | | Fixtures | Reusable test data | | Helpers | Common test operations | | | | Best Practices | Description | | -------------------|------------------------------------------| | Clean database | Reset state between tests | | Test isolation | Each test is independent | | Realistic data | Use fixtures for consistency | | Test auth | Include authentication in tests | | | +------------------------------------------------------------------+Next Chapter
Section titled “Next Chapter”Chapter 44: Test Fixtures & Factories
Last Updated: February 2026