unexpected-express

Extend the unexpected assertion library with support for testing Express middleware

Unexpected-express

Plugin for Unexpected that makes it easy to test Express.js middleware. Uses the unexpected-messy plugin to do most of the heavy lifting.

Example:

var expect = require('unexpected')
    .clone()
    .installPlugin(require('unexpected-express'));
 
function myMiddleware(reqresnext) {
    var contentType = req.headers['accept'] || 'text/plain';
    if (contentType !== 'text/plain' && contentType !== 'text/html') {
        return next(400);
    }
    res.setHeader('Content-Type', contentType);
    var body = 'Here goes ' + req.url + ' as ' + contentType;
    if (contentType === 'text/html') {
        body = '<html>' + body.replace(/&/g, '&amp;').replace(/</g, '&lt;') + '</html>';
    }
    res.send(body);
}
 
describe('myMiddleware', function () {
    it('should handle a simple request', function () {
        return expect(require('express')().use(myMiddleware), 'to yield exchange', {
            request: {
                url: '/blah',
                headers: {
                    Accept: 'text/plain'
                }
            },
            response: {
                statusCode: 200,
                headers: {
                    'Content-Type': 'text/plain'
                },
                body: 'Here goes /blah as text/plain'
            }
        });
    });
});

If you're going to test a piece of middleware extensively, you can create your own custom assertion around that to increase DRYness and put the request properties into the subject's spot:

expect.addAssertion('to yield a response of', function (expectsubjectvalue) {
    return expect(require('express')().use(myMiddleware), 'to yield exchange', {
        request: subject,
        response: value
    });
});
 
describe('myMiddleware', function () {
    it('should default to text/plain', function () {
        return expect('/barf', 'to yield a response of', 'Here goes /barf as text/plain');
    });
 
    it('should support text/html', function () {
        return expect({url: '/quux', headers: {Accept: 'text/html'}}, 'to yield a response of', '<html>Here goes /quux as text/html</html>');
    });
 
    it('should entitify less than and ampersand chars in text/html', function () {
        return expect({url: '/<h&ey<', headers: {Accept: 'text/html'}}, 'to yield a response of', '<html>Here goes /&lt;h&amp;ey&lt; as text/html</html>');
    });
 
    it('should not entitify in text/plain', function () {
        return expect('/<hey', 'to yield a response of', 'Here goes /<hey as text/plain');
    });
 
    it('should return a 400 if asked for an unsupported Content-Type', function () {
        return expect({url: '/something', headers: {Accept: 'text/calendar'}}, 'to yield a response of', {statusCode: 400, errorPassedToNext: true});
    });
 
    it('should return a 404 for /baz', function () {
        return expect('/baz', 'to yield a response of', {statusCode: 404, body: 'I could not find /baz'});
    });
});

You'll get a nice diff when expectations aren't met:

Additional features:

  • Normalizes header names so you don't need to use the ugly lower-case form in the assertions
  • The expected response bodies can be specified as either strings, objects (implies JSON), or Buffer instances
  • Request bodies can be provided as either strings, objects (implies JSON), Buffer instances, or streams.
  • Request body streams that are instances of https://github.com/felixge/node-form-data are special cased to implicitly set the Content-Type header correctly.

Unexpected-express is licensed under a standard 3-clause BSD license -- see the LICENSE file for details.