The suite is 78 files under test/ plus 18 under test/acceptance/, run by mocha with the spec reporter and --check-leaks. There are no mocks of the HTTP layer: nearly every test builds a real express() app and drives it with supertest, which binds the app to an ephemeral port and issues real requests. What the suite asserts is therefore what a client on the wire would observe.

The standard test shape

The canonical pattern -- build an app inline, make a request, assert on status, headers, and body:

test/middleware.basic.js
var app = express()
  , calls = [];

app.use(function(req, res, next){
  calls.push('one');
  next();
});

app.use(function(req, res, next){
  calls.push('two');
  next();
});
// …
request(app)
.get('/')
.set('Content-Type', 'application/json')
.send('{"foo":"bar"}')
.expect('Content-Type', 'application/json')
.expect(function () { assert.deepEqual(calls, ['one', 'two']) })
.expect(200, '{"foo":"bar"}', done)

request(app) works because an Express app is itself a request handler function -- supertest passes it straight to http.createServer.

The two layers

Unit files (test/*.js) map one-to-one onto API members. test/res.send.js pins down ETag generation byte-for-byte:

test/res.send.js
it('should set ETag', function (done) {
  // …
  .expect('ETag', 'W/"3e7-qPnkJ3CVdVhFJQvUBfF10TmVA7g"')

test/Router.js and test/Route.js test the router layer directly -- no HTTP at all, they call router.handle(req, res, done) with plain objects. That is where dispatch-order guarantees live, such as param callbacks running exactly once per request:

test/Router.js
it('should only call once per request', function (done) {
  var count = 0;
  var req = { url: '/foo/bob/bar', method: 'get' };
  // …
  router.param('user', function (req, res, next, user) {
    count++;
    req.user = user;
    next();
  });

  router.use('/foo/:user/', new Router());
  router.use('/foo/:user/', sub);

  router.handle(req, {}, function (err) {
    if (err) return done(err);
    assert.equal(count, 1);

Acceptance files (test/acceptance/*.js) import an app from examples/ and exercise it as a user would. They keep the examples honest: if examples/error-pages breaks, test/acceptance/error-pages.js fails.

Guarantees worth knowing before you touch lib/

Test titles are the contract. A sample of guarantees that pin implementation behavior, all harvested from it(...) names:

  • app.listen() "should wrap with an HTTP server" and "should callback on HTTP server errors" (test/app.listen.js -- the error-callback behavior is why lib/application.js wraps your callback with once and subscribes it to the server error event).
  • Router "should not stack overflow with a large sync middleware stack" -- three separate stack-overflow tests in test/Router.js run 6,000-plus synchronous layers.
  • Route errors "should handle errors via arity 4 functions" (test/Route.js) -- the four-argument signature is what marks a middleware as an error handler.
  • req.query behavior per query parser setting -- test/req.query.js has a describe block per mode (simple, extended, function, disabled).
  • Mounted apps: app.use(app) "should emit "mount" when mounted" and "should strip path from req.url" (test/app.use.js).

Adding a test

  1. Find the file named after the member you are changing (res.download changes go in test/res.download.js).
  2. Follow the local pattern: describe per member or option, it titles phrased as "should ...".
  3. Static fixtures go in test/fixtures/.
  4. Run the single file first, then the full npm test -- --check-leaks and the acceptance layer catch side effects that a single file will not.

Sources: test/*.js, test/acceptance/*.js, test/support/env.js · last synced 2026-07-27 · a371447 · version 5.2.1