Unit vs Integration Tests
A unit test verifies a single function in isolation, often with dependencies mocked out. An integration test verifies multiple pieces working together — like an actual HTTP request hitting a real route, middleware, and (often) a real or test database.
A Unit Test (Vitest)
Console Output
Click “Run” to see the console output here.
An Integration Test with supertest
Console Output
Click “Run” to see the console output here.
Common Testing Tools
| Tool | Purpose |
|---|---|
| Vitest / Jest | Test runner and assertion library |
| supertest | Send HTTP requests directly to an Express app in tests, no real server needed |
| A test database | A separate database instance, reset between test runs, so tests never touch production data |
Testing an Express App Without a Live Server
supertest can send requests directly to your Express app object in memory, without actually binding to a port — this makes integration tests fast and avoids "is port 3000 already in use" conflicts in CI.
Structuring app.js vs server.js
A common pattern: app.js exports the configured Express app (routes, middleware) without calling .listen(), while a separate server.js imports it and calls .listen(). This lets tests import the app directly, without starting a real server.
Best Practice
Reset your test database between test runs (or use transactions that roll back) so tests remain independent and repeatable — a test that depends on data left behind by a previous test is fragile and hard to debug.