chai-express-handler
Version:
Helpers for testing Express handlers with the Chai assertion library.
188 lines (166 loc) • 5.39 kB
JavaScript
var Response = require('./response');
module.exports = function(chai, utils) {
/**
* Expose handler test
*/
var Test = require('./test');
chai.express = chai.express || {};
chai.express.use = function(callbacks) {
return new Test(callbacks);
};
/**
* Aliases.
*/
var Assertion = chai.Assertion;
/**
* Assert that a response has a supplied status.
*
* ```js
* expect(res).to.have.status(200);
* ```
*
* @function
* @name status
* @param {Number} code
* @access public
*/
Assertion.addMethod('status', function(code) {
var obj = this._obj;
new Assertion(obj).to.be.an.instanceof(Response);
this.assert(
obj.statusCode == code
, 'expected response to have status code #{exp} but got #{act}'
, 'expected response to not have status code #{act}'
, code // expected
, obj.statusCode // actual
);
});
/**
* ### .body (data)
*
* Assert that a response has a supplied body.
*
* ```js
* expect(res).to.have.body('hello, world');
* ```
*
* @param {String} data
* @name status
* @api public
*/
Assertion.addMethod('body', function(body) {
var obj = this._obj;
new Assertion(obj).to.be.an.instanceof(Response);
var assertion;
if (typeof body == 'object') {
assertion = new Assertion(obj.body);
utils.transferFlags(this, assertion, false);
assertion.deep.equal(body);
} else {
this.assert(
obj.body === body
, 'expected response to have body #{exp} but got #{act}'
, 'expected response to not have body #{act}'
, body // expected
, obj.body // actual
);
}
});
/**
* Assert that a response has rendered the supplied view.
*
* ```js
* expect(res).to.render('home');
* ```
*
* @function
* @name render
* @param {String} view
* @access public
*/
Assertion.addMethod('render', function(view) {
var obj = this._obj;
new Assertion(obj).to.be.an.instanceof(Response);
new Assertion(obj).to.have.property('_view');
this.assert(
obj._view == view
, 'expected response to render view #{exp} but rendered #{act}'
, 'expected response to not render view #{act}'
, view // expected
, obj._view // actual
);
});
/**
* Assert that a response has the supplied local variables.
*
* ```js
* expect(res).to.have.locals({ name: 'Alice' });
* ```
*
* To assert that a response has defined the supplied local variables, without
* asserting the values of those variables, use an `include` chain.
*
* ```js
* expect(res).to.include.locals([ 'name' ])
* ```
*
* @function
* @name locals
* @param {Object|Array} locals
* @access public
*/
Assertion.addMethod('locals', function(locals) {
var obj = this._obj;
new Assertion(obj).to.be.an.instanceof(Response);
new Assertion(obj).to.have.property('locals');
var assertion;
var deep = utils.flag(this, 'deep');
var contains = utils.flag(this, 'contains');
var all = utils.flag(this, 'all');
var any = utils.flag(this, 'any');
if (contains && (all !== undefined || any !== undefined)) {
assertion = new Assertion(obj.locals);
utils.transferFlags(this, assertion, false);
// This assertion method is reinterpreting `.include` to mean "defined".
// Since `.include` combined with `.all` asserts that the target’s keys
// be a superset of the expected keys, rather than identical sets, the
// `contains` flag is explicitly disabled to operate as if `.include`
// isn't present in the chain, thus requiring an identical set.
utils.flag(assertion, 'contains', false);
assertion.have.keys(locals);
} else if (contains) {
assertion = new Assertion(obj.locals);
utils.transferFlags(this, assertion, false);
// Combine the `all` flag with `contains` to require that the target's
// keys be a superset of the expected keys.
utils.flag(assertion, 'all', true);
assertion.have.keys(locals);
} else if (deep) {
assertion = new Assertion(obj.locals);
utils.transferFlags(this, assertion, false);
assertion.deep.equal(locals);
} else {
assertion = new Assertion(obj.locals);
utils.transferFlags(this, assertion, false);
// Use of `include()` in the default condition, rather than `equals()`,
// is done because testing locals set by the handler for strict equality
// is nonsensical due to the fact that the test case will never have a
// reference to the exact object which is owned by the handler.
//
// Furthermore, using `include()` in the default condition allows tests
// which check the value of a (subset of) response local variables to be
// written concisely in natural language:
//
// expect(res).to.have.locals({ name: 'Alice' });
//
// If the test wants to check the value of all response local variables,
// a deep assertion can be expressed:
//
// expect(res).to.have.deep.locals({
// name: 'Alice',
// csrfToken: 'i8XNjC4b8KVok4uw5RftR38Wgp2BFwql'
// });
assertion.include(locals);
}
});
};