Testing — three tiers, not one
Three test tiers — unit, integration, e2e — so CI stays fast and a red test names the broken layer.
nx-testing-three-tiers
Why it matters
| Stake | If ignored |
|---|---|
| Slow CI |
|
| Unclear failure |
|
| API drift |
|
How to fix
NestJS invites writing everything as e2e with Test.createTestingModule. That's a mistake. Three tiers:
- Unit — service or utility, no NestJS DI, mocks by hand. Fast, focused.
- Integration — module wired up with
Test.createTestingModule, real DB (testcontainers), no HTTP. Verifies wiring. - E2E — full app with
supertest. Verifies public contracts.
Examples
ts
describe('Everything (e2e)', () => {
it('creates a site end-to-end', async () => {
await request(app.getHttpServer())
.post('/sites')
.send({ name: 'Test', slug: 'test' })
.expect(201);
});
});ts
// 1. Unit — fast, no NestJS
describe('SitesService', () => {
it('rejects duplicate slugs', async () => {
const repo: SitesRepository = { findBySlug: async () => ({ id: '1' } as Site), /* ... */ };
const service = new SitesService(repo);
await expect(service.create({ slug: 'taken' })).rejects.toThrow(DuplicateSlugError);
});
});
// 2. Integration — module wired, real DB
describe('SitesModule (integration)', () => {
let app: TestingModule;
beforeAll(async () => {
app = await Test.createTestingModule({
imports: [SitesModule, TestDbModule],
}).compile();
});
// verifies wiring + SQL
});
// 3. E2E — full app
describe('Sites API (e2e)', () => {
it('POST /sites creates a site', async () => {
await request(app.getHttpServer())
.post('/sites')
.send({ name: 'Test', slug: 'test' })
.expect(201);
});
});