Skip to content

Commit ace3f16

Browse files
committed
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. PR-URL: #28263 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent eb05d68 commit ace3f16

2 files changed

Lines changed: 73 additions & 25 deletions

File tree

‎lib/assert.js‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
'use strict';
2222

23-
const{ Object }=primordials;
23+
const{ Object, ObjectPrototype}=primordials;
2424

2525
const{ Buffer }=require('buffer');
2626
const{codes: {
@@ -36,6 +36,7 @@ const { inspect } = require('internal/util/inspect');
3636
const{ isPromise, isRegExp }=require('internal/util/types');
3737
const{EOL}=require('internal/constants');
3838
const{ NativeModule }=require('internal/bootstrap/loaders');
39+
const{ isError }=require('internal/util');
3940

4041
consterrorCache=newMap();
4142

@@ -561,6 +562,8 @@ function compareExceptionKey(actual, expected, key, message, keys, fn) {
561562
}
562563

563564
functionexpectedException(actual,expected,message,fn){
565+
letgeneratedMessage=false;
566+
564567
if(typeofexpected!=='function'){
565568
// Handle regular expressions.
566569
if(isRegExp(expected)){
@@ -618,8 +621,26 @@ function expectedException(actual, expected, message, fn) {
618621
if(expected.prototype!==undefined&&actualinstanceofexpected){
619622
return;
620623
}
621-
if(Error.isPrototypeOf(expected)){
622-
throwactual;
624+
if(ObjectPrototype.isPrototypeOf(Error,expected)){
625+
if(!message){
626+
generatedMessage=true;
627+
message='The error is expected to be an instance of '+
628+
`"${expected.name}". Received `;
629+
if(isError(actual)){
630+
message+=`"${actual.name}"`;
631+
}else{
632+
message+=`"${inspect(actual,{depth: -1})}"`;
633+
}
634+
}
635+
consterr=newAssertionError({
636+
actual,
637+
expected,
638+
message,
639+
operator: fn.name,
640+
stackStartFn: fn
641+
});
642+
err.generatedMessage=generatedMessage;
643+
throwerr;
623644
}
624645

625646
// Check validation functions return value.

‎test/parallel/test-assert.js‎

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,19 @@ assert.throws(() => thrower(a.AssertionError));
123123
assert.throws(()=>thrower(TypeError));
124124

125125
// When passing a type, only catch errors of the appropriate type.
126-
{
127-
letthrew=false;
128-
try{
129-
a.throws(()=>thrower(TypeError),a.AssertionError);
130-
}catch(e){
131-
threw=true;
132-
assert.ok(einstanceofTypeError,'type');
126+
assert.throws(
127+
()=>a.throws(()=>thrower(TypeError),a.AssertionError),
128+
{
129+
generatedMessage: true,
130+
actual: newTypeError({}),
131+
expected: a.AssertionError,
132+
code: 'ERR_ASSERTION',
133+
name: 'AssertionError',
134+
operator: 'throws',
135+
message: 'The error is expected to be an instance of "AssertionError". '+
136+
'Received "TypeError"'
133137
}
134-
assert.ok(threw,'a.throws with an explicit error is eating extra errors');
135-
}
138+
);
136139

137140
// doesNotThrow should pass through all errors.
138141
{
@@ -237,20 +240,27 @@ a.throws(() => thrower(TypeError), (err) => {
237240

238241
// https://github.com/nodejs/node/issues/3188
239242
{
240-
letthrew=false;
241-
letAnotherErrorType;
242-
try{
243-
constES6Error=classextendsError{};
244-
AnotherErrorType=classextendsError{};
245-
246-
assert.throws(()=>{thrownewAnotherErrorType('foo');},ES6Error);
247-
}catch(e){
248-
threw=true;
249-
assert(einstanceofAnotherErrorType,
250-
`expected AnotherErrorType, received ${e}`);
251-
}
243+
letactual;
244+
assert.throws(
245+
()=>{
246+
constES6Error=classextendsError{};
247+
constAnotherErrorType=classextendsError{};
252248

253-
assert.ok(threw);
249+
assert.throws(()=>{
250+
actual=newAnotherErrorType('foo');
251+
throwactual;
252+
},ES6Error);
253+
},
254+
(err)=>{
255+
assert.strictEqual(
256+
err.message,
257+
'The error is expected to be an instance of "ES6Error". '+
258+
'Received "Error"'
259+
);
260+
assert.strictEqual(err.actual,actual);
261+
returntrue;
262+
}
263+
);
254264
}
255265

256266
// Check messages from assert.throws().
@@ -1299,3 +1309,20 @@ assert.throws(
12991309
assert(!err2.stack.includes('hidden'));
13001310
})();
13011311
}
1312+
1313+
assert.throws(
1314+
()=>assert.throws(()=>{throwSymbol('foo');},RangeError),
1315+
{
1316+
message: 'The error is expected to be an instance of "RangeError". '+
1317+
'Received "Symbol(foo)"'
1318+
}
1319+
);
1320+
1321+
assert.throws(
1322+
// eslint-disable-next-line no-throw-literal
1323+
()=>assert.throws(()=>{throw[1,2];},RangeError),
1324+
{
1325+
message: 'The error is expected to be an instance of "RangeError". '+
1326+
'Received "[Array]"'
1327+
}
1328+
);

0 commit comments

Comments
 (0)