Skip to content

Commit 612ba1a

Browse files
BridgeARMylesBorins
authored andcommitted
assert: improve assert.throws
Throw a TypeError in case a error message is provided in the second argument and a third argument is present as well. This is clearly a mistake and should not be done. Backport-PR-URL: #19230 PR-URL: #17585 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: Evan Lucas <evanlucas@me.com>
1 parent 1e5c7e3 commit 612ba1a

File tree

3 files changed

+92
-51
lines changed

3 files changed

+92
-51
lines changed

doc/api/assert.md

+31-6
Original file line numberDiff line numberDiff line change
@@ -765,17 +765,42 @@ assert.throws(
765765

766766
Note that `error` can not be a string. If a string is provided as the second
767767
argument, then `error` is assumed to be omitted and the string will be used for
768-
`message` instead. This can lead to easy-to-miss mistakes:
768+
`message` instead. This can lead to easy-to-miss mistakes. Please read the
769+
example below carefully if using a string as the second argument gets
770+
considered:
769771

770772
<!-- eslint-disable no-restricted-syntax -->
771773
```js
772-
// THIS IS A MISTAKE! DO NOT DO THIS!
773-
assert.throws(myFunction, 'missing foo', 'did not throw with expected message');
774-
775-
// Do this instead.
776-
assert.throws(myFunction, /missing foo/, 'did not throw with expected message');
774+
function throwingFirst() {
775+
throw new Error('First');
776+
}
777+
function throwingSecond() {
778+
throw new Error('Second');
779+
}
780+
function notThrowing() {}
781+
782+
// The second argument is a string and the input function threw an Error.
783+
// In that case both cases do not throw as neither is going to try to
784+
// match for the error message thrown by the input function!
785+
assert.throws(throwingFirst, 'Second');
786+
assert.throws(throwingSecond, 'Second');
787+
788+
// The string is only used (as message) in case the function does not throw:
789+
assert.throws(notThrowing, 'Second');
790+
// AssertionError [ERR_ASSERTION]: Missing expected exception: Second
791+
792+
// If it was intended to match for the error message do this instead:
793+
assert.throws(throwingSecond, /Second$/);
794+
// Does not throw because the error messages match.
795+
assert.throws(throwingFirst, /Second$/);
796+
// Throws a error:
797+
// Error: First
798+
// at throwingFirst (repl:2:9)
777799
```
778800

801+
Due to the confusing notation, it is recommended not to use a string as the
802+
second argument. This might lead to difficult-to-spot errors.
803+
779804
## Caveats
780805

781806
For the following cases, consider using ES2015 [`Object.is()`][],

lib/assert.js

+50-45
Original file line numberDiff line numberDiff line change
@@ -211,68 +211,73 @@ function expectedException(actual, expected) {
211211
return expected.call({}, actual) === true;
212212
}
213213

214-
function tryBlock(block) {
214+
function getActual(block) {
215+
if (typeof block !== 'function') {
216+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'block', 'Function',
217+
block);
218+
}
215219
try {
216220
block();
217221
} catch (e) {
218222
return e;
219223
}
220224
}
221225

222-
function innerThrows(shouldThrow, block, expected, message) {
223-
var details = '';
226+
// Expected to throw an error.
227+
assert.throws = function throws(block, error, message) {
228+
const actual = getActual(block);
224229

225-
if (typeof block !== 'function') {
226-
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'block', 'Function',
227-
block);
228-
}
230+
if (typeof error === 'string') {
231+
if (arguments.length === 3)
232+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE',
233+
'error',
234+
['Function', 'RegExp'],
235+
error);
229236

230-
if (typeof expected === 'string') {
231-
message = expected;
232-
expected = null;
237+
message = error;
238+
error = null;
233239
}
234240

235-
const actual = tryBlock(block);
236-
237-
if (shouldThrow === true) {
238-
if (actual === undefined) {
239-
if (expected && expected.name) {
240-
details += ` (${expected.name})`;
241-
}
242-
details += message ? `: ${message}` : '.';
243-
innerFail({
244-
actual,
245-
expected,
246-
operator: 'throws',
247-
message: `Missing expected exception${details}`,
248-
stackStartFn: assert.throws
249-
});
250-
}
251-
if (expected && expectedException(actual, expected) === false) {
252-
throw actual;
253-
}
254-
} else if (actual !== undefined) {
255-
if (!expected || expectedException(actual, expected)) {
256-
details = message ? `: ${message}` : '.';
257-
innerFail({
258-
actual,
259-
expected,
260-
operator: 'doesNotThrow',
261-
message: `Got unwanted exception${details}\n${actual.message}`,
262-
stackStartFn: assert.doesNotThrow
263-
});
241+
if (actual === undefined) {
242+
let details = '';
243+
if (error && error.name) {
244+
details += ` (${error.name})`;
264245
}
246+
details += message ? `: ${message}` : '.';
247+
innerFail({
248+
actual,
249+
expected: error,
250+
operator: 'throws',
251+
message: `Missing expected exception${details}`,
252+
stackStartFn: throws
253+
});
254+
}
255+
if (error && expectedException(actual, error) === false) {
265256
throw actual;
266257
}
267-
}
268-
269-
// Expected to throw an error.
270-
assert.throws = function throws(block, error, message) {
271-
innerThrows(true, block, error, message);
272258
};
273259

274260
assert.doesNotThrow = function doesNotThrow(block, error, message) {
275-
innerThrows(false, block, error, message);
261+
const actual = getActual(block);
262+
if (actual === undefined)
263+
return;
264+
265+
if (typeof error === 'string') {
266+
message = error;
267+
error = null;
268+
}
269+
270+
if (!error || expectedException(actual, error)) {
271+
const details = message ? `: ${message}` : '.';
272+
innerFail({
273+
actual,
274+
expected: error,
275+
operator: 'doesNotThrow',
276+
message: `Got unwanted exception${details}\n${actual.message}`,
277+
stackStartFn: doesNotThrow
278+
});
279+
}
280+
throw actual;
276281
};
277282

278283
assert.ifError = function ifError(err) { if (err) throw err; };

test/parallel/test-assert.js

+11
Original file line numberDiff line numberDiff line change
@@ -773,3 +773,14 @@ common.expectsError(
773773
message: 'null == true'
774774
}
775775
);
776+
777+
common.expectsError(
778+
// eslint-disable-next-line no-restricted-syntax
779+
() => assert.throws(() => {}, 'Error message', 'message'),
780+
{
781+
code: 'ERR_INVALID_ARG_TYPE',
782+
type: TypeError,
783+
message: 'The "error" argument must be one of type Function or RegExp. ' +
784+
'Received type string'
785+
}
786+
);

0 commit comments

Comments
 (0)