From 75e5ba11deb395e2eb6c85c0106fbf3b6cd654ee Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 14 Jun 2019 00:24:31 +0200 Subject: [PATCH 01/10] assert: improve class instance errors This improves `assert.throws()` and `assert.rejects()` in case error classes are used as validation for the expected error. In case of a failure it will now throw an `AssertionError` instead of the received error if the check fails. That error has the received error as actual property and the expected property is set to the expected error class. The error message should help users to identify the problem faster than before by providing extra context what went wrong. --- lib/assert.js | 27 ++++++++++++-- test/parallel/test-assert.js | 71 +++++++++++++++++++++++++----------- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index 855f8443327584..ee50d124037a43 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -20,7 +20,7 @@ 'use strict'; -const { Object } = primordials; +const { Object, ObjectPrototype } = primordials; const { Buffer } = require('buffer'); const { codes: { @@ -36,6 +36,7 @@ const { inspect } = require('internal/util/inspect'); const { isPromise, isRegExp } = require('internal/util/types'); const { EOL } = require('internal/constants'); const { NativeModule } = require('internal/bootstrap/loaders'); +const { isError } = require('internal/util'); const errorCache = new Map(); @@ -563,6 +564,8 @@ function compareExceptionKey(actual, expected, key, message, keys, fn) { } function expectedException(actual, expected, message, fn) { + let generatedMessage = false; + if (typeof expected !== 'function') { // Handle regular expressions. if (isRegExp(expected)) { @@ -620,8 +623,26 @@ function expectedException(actual, expected, message, fn) { if (expected.prototype !== undefined && actual instanceof expected) { return; } - if (Error.isPrototypeOf(expected)) { - throw actual; + if (ObjectPrototype.isPrototypeOf(Error, expected)) { + if (!message) { + generatedMessage = true; + message = 'The error is expected to be an instance of ' + + `"${expected.name}". Received `; + if (isError(actual)) { + message += `"${actual.name}"`; + } else { + message += `"${inspect(actual, { depth: -1 })}"`; + } + } + const err = new AssertionError({ + actual, + expected, + message, + operator: fn.name, + stackStartFn: fn + }); + err.generatedMessage = generatedMessage; + throw err; } // Check validation functions return value. diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index b07b462387cec0..8327fd2a585fee 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -110,16 +110,19 @@ assert.throws(() => thrower(a.AssertionError)); assert.throws(() => thrower(TypeError)); // When passing a type, only catch errors of the appropriate type. -{ - let threw = false; - try { - a.throws(() => thrower(TypeError), a.AssertionError); - } catch (e) { - threw = true; - assert.ok(e instanceof TypeError, 'type'); +assert.throws( + () => a.throws(() => thrower(TypeError), a.AssertionError), + { + generatedMessage: true, + actual: new TypeError({}), + expected: a.AssertionError, + code: 'ERR_ASSERTION', + name: 'AssertionError', + operator: 'throws', + message: 'The error is expected to be an instance of "AssertionError". ' + + 'Received "TypeError"' } - assert.ok(threw, 'a.throws with an explicit error is eating extra errors'); -} +); // doesNotThrow should pass through all errors. { @@ -213,20 +216,27 @@ a.throws(() => thrower(TypeError), (err) => { // https://github.com/nodejs/node/issues/3188 { - let threw = false; - let AnotherErrorType; - try { - const ES6Error = class extends Error {}; - AnotherErrorType = class extends Error {}; - - assert.throws(() => { throw new AnotherErrorType('foo'); }, ES6Error); - } catch (e) { - threw = true; - assert(e instanceof AnotherErrorType, - `expected AnotherErrorType, received ${e}`); - } + let actual; + assert.throws( + () => { + const ES6Error = class extends Error {}; + const AnotherErrorType = class extends Error {}; - assert.ok(threw); + assert.throws(() => { + actual = new AnotherErrorType('foo'); + throw actual; + }, ES6Error); + }, + (err) => { + assert.strictEqual( + err.message, + 'The error is expected to be an instance of "ES6Error". ' + + 'Received "Error"' + ); + assert.strictEqual(err.actual, actual); + return true; + } + ); } // Check messages from assert.throws(). @@ -1254,3 +1264,20 @@ assert.throws( assert(!err2.stack.includes('hidden')); })(); } + +assert.throws( + () => assert.throws(() => { throw Symbol('foo'); }, RangeError), + { + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received "Symbol(foo)"' + } +); + +assert.throws( + // eslint-disable-next-line no-throw-literal + () => assert.throws(() => { throw [1, 2]; }, RangeError), + { + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received "[Array]"' + } +); From f90cfca8f1ea4922122dc95582678e9f52e138c4 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 14 Jun 2019 10:49:20 +0200 Subject: [PATCH 02/10] assert: fix generatedMessage property This makes sure the `generatedMessage` property is always set as expected. This was not the case some `assert.throws` and `assert.rejects` calls. --- lib/assert.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index ee50d124037a43..bb5fb37ee007d2 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -573,14 +573,21 @@ function expectedException(actual, expected, message, fn) { if (expected.test(str)) return; - throw new AssertionError({ + if (!message) { + generatedMessage = true; + message = 'The input did not match the regular expression ' + + `${inspect(expected)}. Input:\n\n${inspect(str)}\n`; + } + + const err = new AssertionError({ actual, expected, - message: message || 'The input did not match the regular expression ' + - `${inspect(expected)}. Input:\n\n${inspect(str)}\n`, + message, operator: fn.name, stackStartFn: fn }); + err.generatedMessage = generatedMessage; + throw err; } // Handle primitives properly. From 4d29fd186284c3bc82a7405721c0aff43f2c82d1 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 14 Jun 2019 16:14:50 +0200 Subject: [PATCH 03/10] assert: wrap validation function errors This makes sure that validation function used by `assert.throws` and `assert.rejects` always throw validatin errors instead of rethrowing the received error. That should improve the debugging experience for developers since they have a better context where the error is coming from and they also get to know what triggered it. --- lib/assert.js | 16 +++++++++++++++- test/parallel/test-assert-async.js | 15 +++++++++++++++ test/parallel/test-assert.js | 17 +++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/assert.js b/lib/assert.js index bb5fb37ee007d2..99070a6a64504d 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -655,7 +655,21 @@ function expectedException(actual, expected, message, fn) { // Check validation functions return value. const res = expected.call({}, actual); if (res !== true) { - throw actual; + if (!message) { + generatedMessage = true; + const name = expected.name ? `"${expected.name}" ` : ''; + message = `The ${name}validation function is expected to return "true".` + + ` Received ${inspect(res)}`; + } + const err = new AssertionError({ + actual, + expected, + message, + operator: fn.name, + stackStartFn: fn + }); + err.generatedMessage = generatedMessage; + throw err; } } diff --git a/test/parallel/test-assert-async.js b/test/parallel/test-assert-async.js index 8aad1d865c097e..45447d456379e1 100644 --- a/test/parallel/test-assert-async.js +++ b/test/parallel/test-assert-async.js @@ -66,6 +66,21 @@ const invalidThenableFunc = () => { code: 'ERR_INVALID_RETURN_VALUE' }) ); + + const err = new Error('foobar'); + const validate = () => { return 'baz'; }; + promises.push(assert.rejects( + () => assert.rejects(Promise.reject(err), validate), + { + message: 'The "validate" validation function is expected to ' + + "return \"true\". Received 'baz'", + code: 'ERR_ASSERTION', + actual: err, + expected: validate, + name: 'AssertionError', + operator: 'rejects', + } + )); } { diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 8327fd2a585fee..890058e6cf00b9 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -1281,3 +1281,20 @@ assert.throws( 'Received "[Array]"' } ); + +{ + const err = new TypeError('foo'); + const validate = (() => () => ({ a: true, b: [ 1, 2, 3 ] }))(); + assert.throws( + () => assert.throws(() => { throw err; }, validate), + { + message: 'The validation function is expected to ' + + `return "true". Received ${inspect(validate())}`, + code: 'ERR_ASSERTION', + actual: err, + expected: validate, + name: 'AssertionError', + operator: 'throws', + } + ); +} From 1563d9d37ec7139cfc2dd0f559d3d06461b65186 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 14 Jun 2019 16:29:25 +0200 Subject: [PATCH 04/10] assert: DRY .throws code This refactors some code for less duplication. --- lib/assert.js | 97 +++++++++++++++++++++------------------------------ 1 file changed, 40 insertions(+), 57 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index 99070a6a64504d..8df08f10c56eed 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -565,6 +565,7 @@ function compareExceptionKey(actual, expected, key, message, keys, fn) { function expectedException(actual, expected, message, fn) { let generatedMessage = false; + let throwError = false; if (typeof expected !== 'function') { // Handle regular expressions. @@ -578,20 +579,9 @@ function expectedException(actual, expected, message, fn) { message = 'The input did not match the regular expression ' + `${inspect(expected)}. Input:\n\n${inspect(str)}\n`; } - - const err = new AssertionError({ - actual, - expected, - message, - operator: fn.name, - stackStartFn: fn - }); - err.generatedMessage = generatedMessage; - throw err; - } - - // Handle primitives properly. - if (typeof actual !== 'object' || actual === null) { + throwError = true; + // Handle primitives properly. + } else if (typeof actual !== 'object' || actual === null) { const err = new AssertionError({ actual, expected, @@ -601,36 +591,33 @@ function expectedException(actual, expected, message, fn) { }); err.operator = fn.name; throw err; - } - - // Handle validation objects. - const keys = Object.keys(expected); - // Special handle errors to make sure the name and the message are compared - // as well. - if (expected instanceof Error) { - keys.push('name', 'message'); - } else if (keys.length === 0) { - throw new ERR_INVALID_ARG_VALUE('error', - expected, 'may not be an empty object'); - } - if (isDeepEqual === undefined) lazyLoadComparison(); - for (const key of keys) { - if (typeof actual[key] === 'string' && - isRegExp(expected[key]) && - expected[key].test(actual[key])) { - continue; + } else { + // Handle validation objects. + const keys = Object.keys(expected); + // Special handle errors to make sure the name and the message are + // compared as well. + if (expected instanceof Error) { + keys.push('name', 'message'); + } else if (keys.length === 0) { + throw new ERR_INVALID_ARG_VALUE('error', + expected, 'may not be an empty object'); } - compareExceptionKey(actual, expected, key, message, keys, fn); + if (isDeepEqual === undefined) lazyLoadComparison(); + for (const key of keys) { + if (typeof actual[key] === 'string' && + isRegExp(expected[key]) && + expected[key].test(actual[key])) { + continue; + } + compareExceptionKey(actual, expected, key, message, keys, fn); + } + return; } - return; - } - // Guard instanceof against arrow functions as they don't have a prototype. // Check for matching Error classes. - if (expected.prototype !== undefined && actual instanceof expected) { + } else if (expected.prototype !== undefined && actual instanceof expected) { return; - } - if (ObjectPrototype.isPrototypeOf(Error, expected)) { + } else if (ObjectPrototype.isPrototypeOf(Error, expected)) { if (!message) { generatedMessage = true; message = 'The error is expected to be an instance of ' + @@ -641,26 +628,22 @@ function expectedException(actual, expected, message, fn) { message += `"${inspect(actual, { depth: -1 })}"`; } } - const err = new AssertionError({ - actual, - expected, - message, - operator: fn.name, - stackStartFn: fn - }); - err.generatedMessage = generatedMessage; - throw err; + throwError = true; + } else { + // Check validation functions return value. + const res = expected.call({}, actual); + if (res !== true) { + if (!message) { + generatedMessage = true; + const name = expected.name ? `"${expected.name}" ` : ''; + message = `The ${name}validation function is expected to return` + + ` "true". Received ${inspect(res)}`; + } + throwError = true; + } } - // Check validation functions return value. - const res = expected.call({}, actual); - if (res !== true) { - if (!message) { - generatedMessage = true; - const name = expected.name ? `"${expected.name}" ` : ''; - message = `The ${name}validation function is expected to return "true".` + - ` Received ${inspect(res)}`; - } + if (throwError) { const err = new AssertionError({ actual, expected, From 9d7d1034697ee8efee7647c149ad368f30bee7b0 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sat, 15 Jun 2019 13:27:21 +0200 Subject: [PATCH 05/10] assert: add more information to AssertionErrors This adds information about the actual thrown error to the AssertionError's message property. It also improves the logged error instances error name by using the constructors name, if available. --- lib/assert.js | 11 ++++++++++- test/parallel/test-assert-async.js | 3 ++- test/parallel/test-assert.js | 7 ++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index 8df08f10c56eed..69aaac8e245882 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -622,8 +622,13 @@ function expectedException(actual, expected, message, fn) { generatedMessage = true; message = 'The error is expected to be an instance of ' + `"${expected.name}". Received `; + // TODO: Special handle identical names. if (isError(actual)) { - message += `"${actual.name}"`; + const name = actual.constructor && actual.constructor.name; + message += `"${name || actual.name}"`; + if (actual.message) { + message += `\n\nError message:\n\n${actual.message}`; + } } else { message += `"${inspect(actual, { depth: -1 })}"`; } @@ -638,6 +643,10 @@ function expectedException(actual, expected, message, fn) { const name = expected.name ? `"${expected.name}" ` : ''; message = `The ${name}validation function is expected to return` + ` "true". Received ${inspect(res)}`; + + if (isError(actual)) { + message += `\n\nCaught error:\n\n${actual}`; + } } throwError = true; } diff --git a/test/parallel/test-assert-async.js b/test/parallel/test-assert-async.js index 45447d456379e1..cbb4431f1952df 100644 --- a/test/parallel/test-assert-async.js +++ b/test/parallel/test-assert-async.js @@ -73,7 +73,8 @@ const invalidThenableFunc = () => { () => assert.rejects(Promise.reject(err), validate), { message: 'The "validate" validation function is expected to ' + - "return \"true\". Received 'baz'", + "return \"true\". Received 'baz'\n\nCaught error:\n\n" + + 'Error: foobar', code: 'ERR_ASSERTION', actual: err, expected: validate, diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 890058e6cf00b9..f9aa17a90d5151 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -120,7 +120,7 @@ assert.throws( name: 'AssertionError', operator: 'throws', message: 'The error is expected to be an instance of "AssertionError". ' + - 'Received "TypeError"' + 'Received "TypeError"\n\nError message:\n\n[object Object]' } ); @@ -231,7 +231,7 @@ a.throws(() => thrower(TypeError), (err) => { assert.strictEqual( err.message, 'The error is expected to be an instance of "ES6Error". ' + - 'Received "Error"' + 'Received "AnotherErrorType"\n\nError message:\n\nfoo' ); assert.strictEqual(err.actual, actual); return true; @@ -1289,7 +1289,8 @@ assert.throws( () => assert.throws(() => { throw err; }, validate), { message: 'The validation function is expected to ' + - `return "true". Received ${inspect(validate())}`, + `return "true". Received ${inspect(validate())}\n\nCaught ` + + `error:\n\n${err}`, code: 'ERR_ASSERTION', actual: err, expected: validate, From adb44d32b20811d46ea9a3d476104602d26b2c39 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sat, 15 Jun 2019 13:54:50 +0200 Subject: [PATCH 06/10] assert: special handle identical error names in instance checks This makes sure that using `assert.throws()` or `assert.rejects()` in combination with Error classes log appropriate error messages in case the expected and received constructor name are identical but not part of the same prototype chain. --- lib/assert.js | 10 +++++++--- test/parallel/test-assert.js | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index 69aaac8e245882..f545a6f5f2e469 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -622,10 +622,14 @@ function expectedException(actual, expected, message, fn) { generatedMessage = true; message = 'The error is expected to be an instance of ' + `"${expected.name}". Received `; - // TODO: Special handle identical names. if (isError(actual)) { - const name = actual.constructor && actual.constructor.name; - message += `"${name || actual.name}"`; + const name = actual.constructor && actual.constructor.name || + actual.name; + if (expected.name === name) { + message += 'an error with identical name but a different prototype.'; + } else { + message += `"${name}"`; + } if (actual.message) { message += `\n\nError message:\n\n${actual.message}`; } diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index f9aa17a90d5151..084a0b8820a90f 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -25,6 +25,7 @@ const common = require('../common'); const assert = require('assert'); const { inspect } = require('util'); +const vm = require('vm'); const { internalBinding } = require('internal/test/binding'); const a = assert; @@ -1299,3 +1300,17 @@ assert.throws( } ); } + +assert.throws( + () => { + const script = new vm.Script('new RangeError("foobar");'); + const context = vm.createContext(); + const err = script.runInContext(context); + assert.throws(() => { throw err; }, RangeError); + }, + { + message: 'The error is expected to be an instance of "RangeError". ' + + 'Received an error with identical name but a different ' + + 'prototype.\n\nError message:\n\nfoobar' + } +); From e096890e434928117674d9d4be572527fec083d8 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 17 Jun 2019 16:35:10 +0200 Subject: [PATCH 07/10] doc: update assert.throws() examples This updates two outdated examples to the current implementation. --- doc/api/assert.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/api/assert.md b/doc/api/assert.md index 16aa242ec03029..e52c8d2b6db922 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -1171,10 +1171,15 @@ assert.throws( assert.throws( () => { const otherErr = new Error('Not found'); - otherErr.code = 404; + // Copy all enumerable properties from `err` to `otherErr`. + for (const [key, value] of Object.entries(err)) { + otherErr[key] = value; + } throw otherErr; }, - err // This tests for `message`, `name` and `code`. + // The errors `message` and `name` properties will also be checked when using + // an error as validation object. + err ); ``` @@ -1258,11 +1263,9 @@ assert.throws(notThrowing, 'Second'); // It does not throw because the error messages match. assert.throws(throwingSecond, /Second$/); -// If the error message does not match, the error from within the function is -// not caught. +// If the error message does not match, an AssertionError is thrown. assert.throws(throwingFirst, /Second$/); -// Error: First -// at throwingFirst (repl:2:9) +// AssertionError [ERR_ASSERTION] ``` Due to the confusing notation, it is recommended not to use a string as the From fa0f3f19ecd717b778858e197c2e82c58af43948 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Mon, 17 Jun 2019 16:53:15 +0200 Subject: [PATCH 08/10] doc: update assert.throws example This makes sure the current validation function example reflects the current implementation. --- doc/api/assert.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/api/assert.md b/doc/api/assert.md index e52c8d2b6db922..dd8b47e91abb9a 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -1210,6 +1210,9 @@ assert.throws( Custom error validation: +The function must return `true` to indicate all internal validations passed. +It will otherwise fail with an AssertionError. + ```js assert.throws( () => { @@ -1219,9 +1222,10 @@ assert.throws( assert(err instanceof Error); assert(/value/.test(err)); // Returning anything from validation functions besides `true` is not - // recommended. Doing so results in the caught error being thrown again. - // That is usually not the desired outcome. Throw an error about the - // specific validation that failed instead (as done in this example). + // recommended. By doing that it's not clear what part of the validation + // failed. Instead, throw an error about the specific validation that failed + // (as done in this example) and add as much helpful debugging information + // to that error as possible. return true; }, 'unexpected error' From 683e311d85f71c93fa803d88268df28bff629beb Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Thu, 20 Jun 2019 17:13:31 +0200 Subject: [PATCH 09/10] doc: make `AssertionError` a link This makes sure that `AssertionError` links to the correct place in the assert documentation. --- doc/api/assert.md | 84 ++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/doc/api/assert.md b/doc/api/assert.md index dd8b47e91abb9a..293b34f743d3c6 100644 --- a/doc/api/assert.md +++ b/doc/api/assert.md @@ -220,8 +220,9 @@ are also recursively evaluated by the following rules. * [`Symbol`][] properties are not compared. * [`WeakMap`][] and [`WeakSet`][] comparison does not rely on their values. -The following example does not throw an `AssertionError` because the primitives -are considered equal by the [Abstract Equality Comparison][] ( `==` ). +The following example does not throw an [`AssertionError`][] because the +primitives are considered equal by the [Abstract Equality Comparison][] +( `==` ). ```js // WARNING: This does not throw an AssertionError! @@ -266,11 +267,11 @@ assert.deepEqual(obj1, obj4); // AssertionError: { a: { b: 1 } } deepEqual {} ``` -If the values are not equal, an `AssertionError` is thrown with a `message` +If the values are not equal, an [`AssertionError`][] is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a default error message is assigned. If the `message` parameter is an instance of an [`Error`][] then it will be thrown instead of the -`AssertionError`. +[`AssertionError`][]. ## assert.deepStrictEqual(actual, expected[, message]) @@ -533,8 +534,8 @@ assert.doesNotThrow( ); ``` -If an `AssertionError` is thrown and a value is provided for the `message` -parameter, the value of `message` will be appended to the `AssertionError` +If an [`AssertionError`][] is thrown and a value is provided for the `message` +parameter, the value of `message` will be appended to the [`AssertionError`][] message: @@ -582,7 +583,7 @@ assert.equal({ a: { b: 1 } }, { a: { b: 1 } }); // AssertionError: { a: { b: 1 } } == { a: { b: 1 } } ``` -If the values are not equal, an `AssertionError` is thrown with a `message` +If the values are not equal, an [`AssertionError`][] is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a default error message is assigned. If the `message` parameter is an instance of an [`Error`][] then it will be thrown instead of the @@ -594,9 +595,9 @@ added: v0.1.21 --> * `message` {string|Error} **Default:** `'Failed'` -Throws an `AssertionError` with the provided error message or a default error -message. If the `message` parameter is an instance of an [`Error`][] then it -will be thrown instead of the `AssertionError`. +Throws an [`AssertionError`][] with the provided error message or a default +error message. If the `message` parameter is an instance of an [`Error`][] then +it will be thrown instead of the [`AssertionError`][]. ```js const assert = require('assert').strict; @@ -683,7 +684,7 @@ changes: - version: v10.0.0 pr-url: https://github.com/nodejs/node/pull/18247 description: Instead of throwing the original error it is now wrapped into - an `AssertionError` that contains the full stack trace. + an [`AssertionError`][] that contains the full stack trace. - version: v10.0.0 pr-url: https://github.com/nodejs/node/pull/18247 description: Value may now only be `undefined` or `null`. Before all falsy @@ -789,11 +790,11 @@ assert.notDeepEqual(obj1, obj4); // OK ``` -If the values are deeply equal, an `AssertionError` is thrown with a `message` -property set equal to the value of the `message` parameter. If the `message` -parameter is undefined, a default error message is assigned. If the `message` -parameter is an instance of an [`Error`][] then it will be thrown instead of the -`AssertionError`. +If the values are deeply equal, an [`AssertionError`][] is thrown with a +`message` property set equal to the value of the `message` parameter. If the +`message` parameter is undefined, a default error message is assigned. If the +`message` parameter is an instance of an [`Error`][] then it will be thrown +instead of the `AssertionError`. ## assert.notDeepStrictEqual(actual, expected[, message])