Skip to content

Commit

Permalink
assert: accept regular expressions to validate
Browse files Browse the repository at this point in the history
This makes sure regular expressions on validation objects validate
against strings when used with `assert.throws` and `assert.rejects`.

PR-URL: #20485
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michaël Zasso <[email protected]>
  • Loading branch information
BridgeAR committed May 10, 2018
1 parent 81b99da commit cf7be86
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 11 deletions.
50 changes: 44 additions & 6 deletions doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -1070,27 +1070,65 @@ changes:
Expects the function `block` to throw an error.

If specified, `error` can be a [`Class`][], [`RegExp`][], a validation function,
an object where each property will be tested for, or an instance of error where
each property will be tested for including the non-enumerable `message` and
`name` properties.
a validation object where each property will be tested for strict deep equality,
or an instance of error where each property will be tested for strict deep
equality including the non-enumerable `message` and `name` properties. When
using an object, it is also possible to use a regular expression, when
validating against a string property. See below for examples.

If specified, `message` will be the message provided by the `AssertionError` if
the block fails to throw.

Custom error object / error instance:
Custom validation object / error instance:

```js
const err = new TypeError('Wrong value');
err.code = 404;
err.foo = 'bar';
err.info = {
nested: true,
baz: 'text'
};
err.reg = /abc/i;

assert.throws(
() => {
throw err;
},
{
name: 'TypeError',
message: 'Wrong value'
// Note that only properties on the error object will be tested!
message: 'Wrong value',
info: {
nested: true,
baz: 'text'
}
// Note that only properties on the validation object will be tested for.
// Using nested objects requires all properties to be present. Otherwise
// the validation is going to fail.
}
);

// Using regular expressions to validate error properties:
assert.throws(
() => {
throw err;
},
{
// The `name` and `message` properties are strings and using regular
// expressions on those will match against the string. If they fail, an
// error is thrown.
name: /^TypeError$/,
message: /Wrong/,
foo: 'bar',
info: {
nested: true,
// It is not possible to use regular expressions for nested properties!
baz: 'text'
},
// The `reg` property contains a regular expression and only if the
// validation object contains an identical regular expression, it is going
// to pass.
reg: /abc/i
}
);

Expand Down
23 changes: 18 additions & 5 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const {
}
} = require('internal/errors');
const { openSync, closeSync, readSync } = require('fs');
const { inspect, types: { isPromise } } = require('util');
const { inspect, types: { isPromise, isRegExp } } = require('util');
const { EOL } = require('os');
const { NativeModule } = require('internal/bootstrap/loaders');

Expand Down Expand Up @@ -362,10 +362,18 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
};

class Comparison {
constructor(obj, keys) {
constructor(obj, keys, actual) {
for (const key of keys) {
if (key in obj)
this[key] = obj[key];
if (key in obj) {
if (actual !== undefined &&
typeof actual[key] === 'string' &&
isRegExp(obj[key]) &&
obj[key].test(actual[key])) {
this[key] = actual[key];
} else {
this[key] = obj[key];
}
}
}
}
}
Expand All @@ -375,7 +383,7 @@ function compareExceptionKey(actual, expected, key, message, keys) {
if (!message) {
// Create placeholder objects to create a nice output.
const a = new Comparison(actual, keys);
const b = new Comparison(expected, keys);
const b = new Comparison(expected, keys, actual);

const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
Expand Down Expand Up @@ -415,6 +423,11 @@ function expectedException(actual, expected, msg) {
keys.push('name', 'message');
}
for (const key of keys) {
if (typeof actual[key] === 'string' &&
isRegExp(expected[key]) &&
expected[key].test(actual[key])) {
continue;
}
compareExceptionKey(actual, expected, key, msg, keys);
}
return true;
Expand Down
26 changes: 26 additions & 0 deletions test/parallel/test-assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -914,3 +914,29 @@ assert.throws(
}
);
}

assert.throws(
() => { throw new TypeError('foobar'); },
{
message: /foo/,
name: /^TypeError$/
}
);

assert.throws(
() => assert.throws(
() => { throw new TypeError('foobar'); },
{
message: /fooa/,
name: /^TypeError$/
}
),
{
message: `${start}\n${actExp}\n\n` +
' Comparison {\n' +
"- message: 'foobar',\n" +
'+ message: /fooa/,\n' +
" name: 'TypeError'\n" +
' }'
}
);

0 comments on commit cf7be86

Please sign in to comment.