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:
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:
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:
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 whylib/application.jswraps your callback withonceand subscribes it to the servererrorevent).- Router "should not stack overflow with a large sync middleware stack" -- three separate stack-overflow tests in
test/Router.jsrun 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.querybehavior perquery parsersetting --test/req.query.jshas 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
- Find the file named after the member you are changing (
res.downloadchanges go intest/res.download.js). - Follow the local pattern:
describeper member or option,ittitles phrased as "should ...". - Static fixtures go in
test/fixtures/. - Run the single file first, then the full
npm test----check-leaksand the acceptance layer catch side effects that a single file will not.