Explain the three levels of the test pyramid and when to use each
Write a complete unit test for a service using Jest mocks
Write an integration test that runs against a real PostgreSQL database
Write an E2E test using Supertest for a full HTTP request → response
Apply the golden rule: test the sad paths harder than the happy path
The why
In fintech, bugs aren't just annoying — they move real money. "If the payment service has a bug that double-charges customers, how do you find out?" Either a test catches it before it ships, or a customer calls support after losing money.
Tests are how you prove, before every deploy, that the code does what you think it does.
The test pyramid — three levels of tests
plaintext
▲ /|\ / | \ E2E tests (few) / | \ Full HTTP → App → Real DB / | \ Slow. Run pre-deploy. /─────────\ / | \ Integration tests (more) / | \ Service + Real DB / | \ Run on every commit. /──────────────────\ / | \ Unit tests (lots) / | \ Pure logic, mocked deps / | \ Run on every file save. ───────────────────────────
Type
What it tests
Speed
When
Unit
One function/method, all deps mocked
Milliseconds
On every save
Integration
Service + real database
Seconds
On every commit
E2E
Full HTTP request → response, real app
Tens of seconds
Pre-merge, pre-deploy
Unit tests — testing logic in isolation
A unit test mocks every dependency and tests only the logic of one method.
ts
// src/modules/orders/orders.service.spec.tsdescribe('OrdersService', () => { let service: OrdersService; let mockOrderModel: any; let mockPaymentsService: any; let mockSequelize: any; beforeEach
Integration tests — testing against a real database
Integration tests connect to a real PostgreSQL database (spun up in for CI). They prove that your queries and constraints actually work.
ts
describe('OrdersService (integration)', () => { let app: INestApplication; let service: OrdersService; let sequelize: Sequelize; beforeAll(async () => { const module = await Test.
E2E tests — full HTTP request → response
E2E tests use Supertest to send real HTTP requests to your NestJS app and verify the full works together.
Sequelize itself (it's already tested by the library authors)
Auto-generated code
Constructor calls with no logic
Running tests in this project
bash
npm test # Run all unit testsnpm run test:cov # Run with code coverage reportnpm run test:e2e # Run E2E testsnpm run test:watch # Watch mode (re-runs on file save)
One thing to remember
One thing to remember
Test the sad paths harder than the happy path. The happy path is: valid , enough balance, no concurrent updates. The sad paths — insufficient balance, duplicate submission, failed rollback — are exactly what happens in production. Test those first.