Testing your Server without binding a port

Spinning up a real server, hitting it from the test, then shutting it down works, but it is slow and flaky. Supertest does something cleaner: it takes your Express app and routes requests through it in memory. No port, no network, no flake.

The test runner here is Jest, and you only need its core vocabulary to read what comes next: describe groups related tests under a label, it declares a single test case, and expect makes an assertion like expect(res.status).toBe(200). We will go deeper on writing your own unit tests later; for now, read them as labeled checks.

tests/health.test.ts
typescript
import supertest from 'supertest';
import { app } from '../before/index';

const request = supertest(app);

describe('GET /health', () => {
  it('returns 200 with status ok', async () => {
    const res = await request.get('/health');
    expect(res.status).toBe(200);
    expect(res.body).toEqual({ status: 'ok' });
  });
});

Hit the health route with no real network in the picture.

Read it carefully. We import the app, not the server. We never call startServer. supertest(app) creates a test client that hands requests directly to Express. We get back the same response object the real client would, with status, headers, and body, all without binding a port.

Because the running server is the part that holds the port. The app is just the routing logic. Supertest only needs the routing logic to do its job. This is why we exported app and startServer separately. Tests use one, production uses the other.

Quiz: Quiz

Loading practice…

Validation checklist: Run the test suite

Loading practice…