assert: improve assert.throws

This switches the assert.throws output to the one used in strict mode
if a error object is used for comparison. From now on it will show
the complete difference between two objects instead of only showing
the first failing property.

It also fixes detecting properties with a undefined value and fails
in case the thrown error does not contain the value at all.

PR-URL: https://github.com/nodejs/node/pull/19463
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Ruben Bridgewater 2018-03-20 01:49:29 +01:00
parent 28e4e43e51
commit a1c96f8e07
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
5 changed files with 80 additions and 45 deletions

View File

@ -366,13 +366,38 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
} }
}; };
function compareExceptionKey(actual, expected, key, msg) { class Comparison {
if (!isDeepStrictEqual(actual[key], expected[key])) { constructor(obj, keys) {
for (const key of keys) {
if (key in obj)
this[key] = obj[key];
}
}
}
function compareExceptionKey(actual, expected, key, message, keys) {
if (!(key in actual) || !isDeepStrictEqual(actual[key], expected[key])) {
if (!message) {
// Create placeholder objects to create a nice output.
const a = new Comparison(actual, keys);
const b = new Comparison(expected, keys);
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
const err = new AssertionError({
actual: a,
expected: b,
operator: 'deepStrictEqual',
stackStartFn: assert.throws,
errorDiff: ERR_DIFF_EQUAL
});
Error.stackTraceLimit = tmpLimit;
message = err.message;
}
innerFail({ innerFail({
actual: actual[key], actual,
expected: expected[key], expected,
message: msg || `${key}: expected ${inspect(expected[key])}, ` + message,
`not ${inspect(actual[key])}`,
operator: 'throws', operator: 'throws',
stackStartFn: assert.throws stackStartFn: assert.throws
}); });
@ -389,16 +414,14 @@ function expectedException(actual, expected, msg) {
'expected', ['Function', 'RegExp'], expected 'expected', ['Function', 'RegExp'], expected
); );
} }
// The name and message could be non enumerable. Therefore test them const keys = Object.keys(expected);
// explicitly. // Special handle errors to make sure the name and the message are compared
if ('name' in expected) { // as well.
compareExceptionKey(actual, expected, 'name', msg); if (expected instanceof Error) {
keys.push('name', 'message');
} }
if ('message' in expected) { for (const key of keys) {
compareExceptionKey(actual, expected, 'message', msg); compareExceptionKey(actual, expected, key, msg, keys);
}
for (const key of Object.keys(expected)) {
compareExceptionKey(actual, expected, key, msg);
} }
return true; return true;
} }

View File

@ -2,7 +2,13 @@ assert.js:*
throw new AssertionError(obj); throw new AssertionError(obj);
^ ^
AssertionError [ERR_ASSERTION]: bar: expected true, not undefined AssertionError [ERR_ASSERTION]: Input A expected to deepStrictEqual input B:
+ expected - actual
- Comparison {}
+ Comparison {
+ bar: true
+ }
at Object.<anonymous> (*assert_throws_stack.js:*:*) at Object.<anonymous> (*assert_throws_stack.js:*:*)
at * at *
at * at *

View File

@ -38,11 +38,7 @@ assert.throws(() => {
assert.fail(typeof 1, 'object', new TypeError('another custom message')); assert.fail(typeof 1, 'object', new TypeError('another custom message'));
}, { }, {
name: 'TypeError', name: 'TypeError',
message: 'another custom message', message: 'another custom message'
operator: undefined,
actual: undefined,
expected: undefined,
code: undefined
}); });
// No third arg (but a fourth arg) // No third arg (but a fourth arg)

View File

@ -33,8 +33,5 @@ assert.throws(() => {
assert.fail(new TypeError('custom message')); assert.fail(new TypeError('custom message'));
}, { }, {
name: 'TypeError', name: 'TypeError',
message: 'custom message', message: 'custom message'
operator: undefined,
actual: undefined,
expected: undefined
}); });

View File

@ -34,6 +34,12 @@ const { writeFileSync, unlinkSync } = require('fs');
const { inspect } = require('util'); const { inspect } = require('util');
const a = assert; const a = assert;
const colors = process.stdout.isTTY && process.stdout.getColorDepth() > 1;
const start = 'Input A expected to deepStrictEqual input B:';
const actExp = colors ?
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m' :
'+ expected - actual';
assert.ok(a.AssertionError.prototype instanceof Error, assert.ok(a.AssertionError.prototype instanceof Error,
'a.AssertionError instanceof Error'); 'a.AssertionError instanceof Error');
@ -436,11 +442,6 @@ common.expectsError(
Error.stackTraceLimit = tmpLimit; Error.stackTraceLimit = tmpLimit;
// Test error diffs. // Test error diffs.
const colors = process.stdout.isTTY && process.stdout.getColorDepth() > 1;
const start = 'Input A expected to deepStrictEqual input B:';
const actExp = colors ?
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m' :
'+ expected - actual';
const plus = colors ? '\u001b[32m+\u001b[39m' : '+'; const plus = colors ? '\u001b[32m+\u001b[39m' : '+';
const minus = colors ? '\u001b[31m-\u001b[39m' : '-'; const minus = colors ? '\u001b[31m-\u001b[39m' : '-';
let message = [ let message = [
@ -765,24 +766,32 @@ common.expectsError(
errObj.code = 404; errObj.code = 404;
assert.throws(errFn, errObj); assert.throws(errFn, errObj);
errObj.code = '404'; // Fail in case a expected property is undefined and not existent on the
common.expectsError( // error.
errObj.foo = undefined;
assert.throws(
() => assert.throws(errFn, errObj), () => assert.throws(errFn, errObj),
{ {
code: 'ERR_ASSERTION', code: 'ERR_ASSERTION',
type: assert.AssertionError, name: 'AssertionError [ERR_ASSERTION]',
message: 'code: expected \'404\', not 404' message: `${start}\n${actExp}\n\n` +
" Comparison {\n name: 'TypeError',\n" +
" message: 'Wrong value',\n- code: 404\n" +
'+ code: 404,\n+ foo: undefined\n }'
} }
); );
errObj.code = 404; // Show multiple wrong properties at the same time.
errObj.foo = 'bar'; errObj.code = '404';
common.expectsError( assert.throws(
() => assert.throws(errFn, errObj), () => assert.throws(errFn, errObj),
{ {
code: 'ERR_ASSERTION', code: 'ERR_ASSERTION',
type: assert.AssertionError, name: 'AssertionError [ERR_ASSERTION]',
message: 'foo: expected \'bar\', not undefined' message: `${start}\n${actExp}\n\n` +
" Comparison {\n name: 'TypeError',\n" +
" message: 'Wrong value',\n- code: 404\n" +
"+ code: '404',\n+ foo: undefined\n }"
} }
); );
@ -806,20 +815,24 @@ common.expectsError(
); );
assert.throws(() => { throw new Error('e'); }, new Error('e')); assert.throws(() => { throw new Error('e'); }, new Error('e'));
common.expectsError( assert.throws(
() => assert.throws(() => { throw new TypeError('e'); }, new Error('e')), () => assert.throws(() => { throw new TypeError('e'); }, new Error('e')),
{ {
type: assert.AssertionError, name: 'AssertionError [ERR_ASSERTION]',
code: 'ERR_ASSERTION', code: 'ERR_ASSERTION',
message: "name: expected 'Error', not 'TypeError'" message: `${start}\n${actExp}\n\n` +
" Comparison {\n- name: 'TypeError',\n+ name: 'Error'," +
"\n message: 'e'\n }"
} }
); );
common.expectsError( assert.throws(
() => assert.throws(() => { throw new Error('foo'); }, new Error('')), () => assert.throws(() => { throw new Error('foo'); }, new Error('')),
{ {
type: assert.AssertionError, name: 'AssertionError [ERR_ASSERTION]',
code: 'ERR_ASSERTION', code: 'ERR_ASSERTION',
message: "message: expected '', not 'foo'" message: `${start}\n${actExp}\n\n` +
" Comparison {\n name: 'Error',\n- message: 'foo'" +
"\n+ message: ''\n }"
} }
); );