Skip to content

Commit

Permalink
feat: add .error assertion to BDD interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
meeber committed Dec 27, 2017
1 parent a3ab487 commit c294eae
Show file tree
Hide file tree
Showing 3 changed files with 732 additions and 0 deletions.
124 changes: 124 additions & 0 deletions lib/chai/core/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2393,6 +2393,130 @@ module.exports = function (chai, _) {
Assertion.addMethod('keys', assertKeys);
Assertion.addMethod('key', assertKeys);

/**
* ### .error([errLike[, errMsgMatcher[, msg]]])
*
* When no arguments are provided, `.error` asserts that the target is an
* instance of the built-in `Error` constructor.
*
* expect(new Error('Illegal salmon!')).to.be.an.error();
*
* When one argument is provided, and it's an `Error` constructor, `.error`
* asserts that the target is an instance of that `Error` constructor.
*
* expect(new TypeError('Illegal salmon!')).to.be.an.error(TypeError);
*
* When one argument is provided, and it's an `Error` instance, `.error`
* asserts that the target is strictly (`===`) equal to that `Error` instance.
*
* var err = new TypeError('Illegal salmon!');
*
* expect(err).to.be.an.error(err);
*
* When one argument is provided, and it's a string, `.error` asserts that
* the target is an instance of `Error` with a message that contains that
* string.
*
* expect(new Error('Illegal salmon!')).to.be.an.error('salmon');
*
* When one argument is provided, and it's a regular expression, `.error`
* asserts that the target is an instance of `Error` with a message that
* matches that regular expression.
*
* expect(new Error('Illegal salmon!')).to.be.an.error(/salmon/);
*
* When two arguments are provided, and the first is an `Error` constructor,
* and the second is a string or regular expression, `.error` asserts that the
* target is an `Error` instance that fulfills both conditions as described
* above.
*
* var err = new TypeError('Illegal salmon!');
*
* expect(err).to.be.an.error(TypeError, 'salmon');
* expect(err).to.be.an.error(TypeError, /salmon/);
* expect(err).to.be.an.error(err, 'salmon');
* expect(err).to.be.an.error(err, /salmon/);
*
* Add `.not` earlier in the chain to negate `.error`.
*
* expect(42).to.not.be.an.error();
*
* However, it's dangerous to negate `.error` when providing any arguments.
* The problem is that it creates uncertain expectations by asserting that the
* target either isn't an `Error` instance, or that it is an `Error` instance
* but not of the given `Error` constructor, or that it is an instance of the
* given `Error` constructor but with a message that doesn't include the given
* string. It's often best to identify the exact output that's expected, and
* then write an assertion that only accepts that exact output.
*
* When the target isn't expected to be an `Error` instance, it's often best
* to assert what it's expected to equal.
*
* var myNum = 42;
*
* expect(myNum).to.equal(42); // Recommended
* expect(myNum).to.not.be.an.error(); // Not recommended
*
* When the target is expected to be an `Error` instance, it's often best to
* assert that it's an instance of a certain `Error` constructor, and has a
* message that includes a certain string, rather than asserting that it isn't
* an instance of one of countless `Error` constructors, and doesn't have a
* message that includes one of countless strings.
*
* var err = new TypeError('Illegal salmon!');
*
* expect(err).to.be.an.error(TypeError, 'salmon'); // Recommended
* expect(err).to.not.be.an.error(ReferenceError, 'x'); // Not recommended
*
* `.error` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`. When not providing two arguments, always
* use the second form.
*
* var myNum = 42;
*
* expect(myNum).to.be.an.error(TypeError, 'x', 'nooo why fail??');
* expect(myNum, 'nooo why fail??').to.be.an.error();
*
* Due to limitations in ES5, `.error` may not always work as expected when
* using a transpiler such as Babel or TypeScript. In particular, it may
* produce unexpected results when subclassing the built-in `Error`
* constructor and then passing the subclassed constructor to `.error`. See
* your transpiler's docs for details:
*
* - ([Babel](https://babeljs.io/docs/usage/caveats/#classes))
* - ([TypeScript](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work))
*
* @name error
* @param {Error|ErrorConstructor} errLike
* @param {String|RegExp} errMsgMatcher error message
* @param {String} msg _optional_
* @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
* @namespace BDD
* @api public
*/

Assertion.addMethod('error', function (errLike, errMsgMatcher, msg) {
if (msg) flag(this, 'message', msg);

var obj = flag(this, 'object');
var criteria = _.checkError.createCriteria(errLike, errMsgMatcher);
var expectedDesc = _.checkError.describeExpectedError(criteria);
// We append the expected value instead of using #{exp} because #{exp} adds
// single quotes around the expected value, even when it doesn't make sense
// (e.g., "expected [Error] to be 'a TypeError'").
var failMsg = 'expected #{act} to be ' + expectedDesc;
var negatedFailMsg = 'expected #{act} to not be ' + expectedDesc;

this.assert(
_.checkError.checkError(obj, criteria),
failMsg,
negatedFailMsg,
errLike,
obj
);
});

/**
* ### .throw([errLike[, errMsgMatcher[, msg]]])
*
Expand Down
Loading

0 comments on commit c294eae

Please sign in to comment.