Skip to content

Commit ebd60fa

Browse files
BridgeARMylesBorins
authored andcommitted
assert: .throws accept objects
From now on it is possible to use a validation object in throws instead of the other possibilites. Backport-PR-URL: #19230 PR-URL: #17584 Refs: #17557 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Ron Korving <[email protected]> Reviewed-By: Yuta Hiroto <[email protected]>
1 parent 7457093 commit ebd60fa

File tree

3 files changed

+138
-17
lines changed

3 files changed

+138
-17
lines changed

doc/api/assert.md

+23-3
Original file line numberDiff line numberDiff line change
@@ -709,18 +709,21 @@ instead of the `AssertionError`.
709709
<!-- YAML
710710
added: v0.1.21
711711
changes:
712+
- version: REPLACEME
713+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
714+
description: The `error` parameter can now be an object as well.
712715
- version: v4.2.0
713716
pr-url: https://github.com/nodejs/node/pull/3276
714717
description: The `error` parameter can now be an arrow function.
715718
-->
716719
* `block` {Function}
717-
* `error` {RegExp|Function}
720+
* `error` {RegExp|Function|object}
718721
* `message` {any}
719722

720723
Expects the function `block` to throw an error.
721724

722-
If specified, `error` can be a constructor, [`RegExp`][], or validation
723-
function.
725+
If specified, `error` can be a constructor, [`RegExp`][], a validation
726+
function, or an object where each property will be tested for.
724727

725728
If specified, `message` will be the message provided by the `AssertionError` if
726729
the block fails to throw.
@@ -766,6 +769,23 @@ assert.throws(
766769
);
767770
```
768771

772+
Custom error object / error instance:
773+
774+
```js
775+
assert.throws(
776+
() => {
777+
const err = new TypeError('Wrong value');
778+
err.code = 404;
779+
throw err;
780+
},
781+
{
782+
name: 'TypeError',
783+
message: 'Wrong value'
784+
// Note that only properties on the error object will be tested!
785+
}
786+
);
787+
```
788+
769789
Note that `error` can not be a string. If a string is provided as the second
770790
argument, then `error` is assumed to be omitted and the string will be used for
771791
`message` instead. This can lead to easy-to-miss mistakes. Please read the

lib/assert.js

+39-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
const { isDeepEqual, isDeepStrictEqual } =
2424
require('internal/util/comparisons');
2525
const errors = require('internal/errors');
26+
const { inspect } = require('util');
2627

2728
// The assert module provides functions that throw
2829
// AssertionError's when particular conditions are not met. The
@@ -196,10 +197,44 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
196197
}
197198
};
198199

199-
function expectedException(actual, expected) {
200+
function createMsg(msg, key, actual, expected) {
201+
if (msg)
202+
return msg;
203+
return `${key}: expected ${inspect(expected[key])}, ` +
204+
`not ${inspect(actual[key])}`;
205+
}
206+
207+
function expectedException(actual, expected, msg) {
200208
if (typeof expected !== 'function') {
201-
// Should be a RegExp, if not fail hard
202-
return expected.test(actual);
209+
if (expected instanceof RegExp)
210+
return expected.test(actual);
211+
// assert.doesNotThrow does not accept objects.
212+
if (arguments.length === 2) {
213+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'expected',
214+
['Function', 'RegExp'], expected);
215+
}
216+
// The name and message could be non enumerable. Therefore test them
217+
// explicitly.
218+
if ('name' in expected) {
219+
assert.strictEqual(
220+
actual.name,
221+
expected.name,
222+
createMsg(msg, 'name', actual, expected));
223+
}
224+
if ('message' in expected) {
225+
assert.strictEqual(
226+
actual.message,
227+
expected.message,
228+
createMsg(msg, 'message', actual, expected));
229+
}
230+
const keys = Object.keys(expected);
231+
for (const key of keys) {
232+
assert.deepStrictEqual(
233+
actual[key],
234+
expected[key],
235+
createMsg(msg, key, actual, expected));
236+
}
237+
return true;
203238
}
204239
// Guard instanceof against arrow functions as they don't have a prototype.
205240
if (expected.prototype !== undefined && actual instanceof expected) {
@@ -252,7 +287,7 @@ assert.throws = function throws(block, error, message) {
252287
stackStartFn: throws
253288
});
254289
}
255-
if (error && expectedException(actual, error) === false) {
290+
if (error && expectedException(actual, error, message) === false) {
256291
throw actual;
257292
}
258293
};

test/parallel/test-assert.js

+76-10
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,6 @@ assert.ok(a.AssertionError.prototype instanceof Error,
3939

4040
assert.throws(makeBlock(a, false), a.AssertionError, 'ok(false)');
4141

42-
// Using a object as second arg results in a failure
43-
assert.throws(
44-
() => { assert.throws(() => { throw new Error(); }, { foo: 'bar' }); },
45-
common.expectsError({
46-
type: TypeError,
47-
message: 'expected.test is not a function'
48-
})
49-
);
50-
51-
5242
assert.doesNotThrow(makeBlock(a, true), a.AssertionError, 'ok(true)');
5343

5444
assert.doesNotThrow(makeBlock(a, 'test', 'ok(\'test\')'));
@@ -784,3 +774,79 @@ common.expectsError(
784774
'Received type string'
785775
}
786776
);
777+
778+
{
779+
const errFn = () => {
780+
const err = new TypeError('Wrong value');
781+
err.code = 404;
782+
throw err;
783+
};
784+
const errObj = {
785+
name: 'TypeError',
786+
message: 'Wrong value'
787+
};
788+
assert.throws(errFn, errObj);
789+
790+
errObj.code = 404;
791+
assert.throws(errFn, errObj);
792+
793+
errObj.code = '404';
794+
common.expectsError(
795+
// eslint-disable-next-line no-restricted-syntax
796+
() => assert.throws(errFn, errObj),
797+
{
798+
code: 'ERR_ASSERTION',
799+
type: assert.AssertionError,
800+
message: 'code: expected \'404\', not 404'
801+
}
802+
);
803+
804+
errObj.code = 404;
805+
errObj.foo = 'bar';
806+
common.expectsError(
807+
// eslint-disable-next-line no-restricted-syntax
808+
() => assert.throws(errFn, errObj),
809+
{
810+
code: 'ERR_ASSERTION',
811+
type: assert.AssertionError,
812+
message: 'foo: expected \'bar\', not undefined'
813+
}
814+
);
815+
816+
common.expectsError(
817+
() => assert.throws(() => { throw new Error(); }, { foo: 'bar' }, 'foobar'),
818+
{
819+
type: assert.AssertionError,
820+
code: 'ERR_ASSERTION',
821+
message: 'foobar'
822+
}
823+
);
824+
825+
common.expectsError(
826+
() => assert.doesNotThrow(() => { throw new Error(); }, { foo: 'bar' }),
827+
{
828+
type: TypeError,
829+
code: 'ERR_INVALID_ARG_TYPE',
830+
message: 'The "expected" argument must be one of type Function or ' +
831+
'RegExp. Received type object'
832+
}
833+
);
834+
835+
assert.throws(() => { throw new Error('e'); }, new Error('e'));
836+
common.expectsError(
837+
() => assert.throws(() => { throw new TypeError('e'); }, new Error('e')),
838+
{
839+
type: assert.AssertionError,
840+
code: 'ERR_ASSERTION',
841+
message: "name: expected 'Error', not 'TypeError'"
842+
}
843+
);
844+
common.expectsError(
845+
() => assert.throws(() => { throw new Error('foo'); }, new Error('')),
846+
{
847+
type: assert.AssertionError,
848+
code: 'ERR_ASSERTION',
849+
message: "message: expected '', not 'foo'"
850+
}
851+
);
852+
}

0 commit comments

Comments
 (0)