Skip to content

Commit 2d37491

Browse files
committed
assert: .throws accept objects
From now on it is possible to use a validation object in throws instead of the other possibilites. 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 fc61ee3 commit 2d37491

File tree

3 files changed

+138
-17
lines changed

3 files changed

+138
-17
lines changed

doc/api/assert.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -711,18 +711,21 @@ instead of the `AssertionError`.
711711
<!-- YAML
712712
added: v0.1.21
713713
changes:
714+
- version: REPLACEME
715+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
716+
description: The `error` parameter can now be an object as well.
714717
- version: v4.2.0
715718
pr-url: https://github.com/nodejs/node/pull/3276
716719
description: The `error` parameter can now be an arrow function.
717720
-->
718721
* `block` {Function}
719-
* `error` {RegExp|Function}
722+
* `error` {RegExp|Function|object}
720723
* `message` {any}
721724

722725
Expects the function `block` to throw an error.
723726

724-
If specified, `error` can be a constructor, [`RegExp`][], or validation
725-
function.
727+
If specified, `error` can be a constructor, [`RegExp`][], a validation
728+
function, or an object where each property will be tested for.
726729

727730
If specified, `message` will be the message provided by the `AssertionError` if
728731
the block fails to throw.
@@ -768,6 +771,23 @@ assert.throws(
768771
);
769772
```
770773

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

lib/assert.js

Lines changed: 39 additions & 4 deletions
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
@@ -193,10 +194,44 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
193194
}
194195
};
195196

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

test/parallel/test-assert.js

Lines changed: 76 additions & 10 deletions
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)