diff --git a/doc/api/assert.md b/doc/api/assert.md index 43e5800ff031b8..7f98e975f19c9b 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -1070,18 +1070,26 @@ 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( () => { @@ -1089,8 +1097,38 @@ assert.throws( }, { 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 } ); diff --git a/lib/assert.js b/lib/assert.js index 05b43b5b54d074..ac9fd30793aaf7 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -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'); @@ -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]; + } + } } } } @@ -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; @@ -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; diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index d892122ed62ff2..cbcda17ab7188d 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -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" + + ' }' + } +);