Skip to content

Commit dd997f8

Browse files
committed
Return null when throws() and throwsAsync() assertions fail
Make the behavior explicit in the documentation and type definition.
1 parent 4b4b2f6 commit dd997f8

File tree

5 files changed

+49
-49
lines changed

5 files changed

+49
-49
lines changed

docs/03-assertions.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ Finally, this returns a boolean indicating whether the assertion passed.
229229

230230
### `.throws(fn, expectation?, message?)`
231231

232-
Assert that an error is thrown. `fn` must be a function which should throw. The thrown value *must* be an error. It is returned so you can run more assertions against it.
232+
Assert that an error is thrown. `fn` must be a function which should throw. The thrown value *must* be an error. It is returned so you can run more assertions against it. If the assertion fails then `null` is returned.
233233

234234
`expectation` can be an object with one or more of the following properties:
235235

@@ -257,13 +257,11 @@ test('throws', t => {
257257
});
258258
```
259259

260-
Does not return anything.
261-
262260
### `.throwsAsync(thrower, expectation?, message?)`
263261

264262
Assert that an error is thrown. `thrower` can be an async function which should throw, or a promise that should reject. This assertion must be awaited.
265263

266-
The thrown value *must* be an error. It is returned so you can run more assertions against it.
264+
The thrown value *must* be an error. It is returned so you can run more assertions against it. If the assertion fails then `null` is returned.
267265

268266
`expectation` can be an object with one or more of the following properties:
269267

@@ -294,8 +292,6 @@ test('rejects', async t => {
294292
});
295293
```
296294

297-
Does not return anything.
298-
299295
### `.notThrows(fn, message?)`
300296

301297
Assert that no error is thrown. `fn` must be a function which shouldn't throw. Does not return anything.

lib/assert.js

+12-11
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ export class Assertions {
469469
let [fn, expectations, message] = args;
470470

471471
if (!checkMessage('throws', message)) {
472-
return;
472+
return null;
473473
}
474474

475475
if (typeof fn !== 'function') {
@@ -479,14 +479,14 @@ export class Assertions {
479479
message: '`t.throws()` must be called with a function',
480480
values: [formatWithLabel('Called with:', fn)]
481481
}));
482-
return;
482+
return null;
483483
}
484484

485485
try {
486486
expectations = validateExpectations('throws', expectations, args.length, experiments);
487487
} catch (error) {
488488
fail(error);
489-
return;
489+
return null;
490490
}
491491

492492
let retval;
@@ -501,7 +501,7 @@ export class Assertions {
501501
message,
502502
values: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)]
503503
}));
504-
return;
504+
return null;
505505
}
506506
} catch (error) {
507507
actual = error;
@@ -513,7 +513,7 @@ export class Assertions {
513513
message,
514514
values: [formatWithLabel('Function returned:', retval)]
515515
}));
516-
return;
516+
return null;
517517
}
518518

519519
try {
@@ -531,11 +531,11 @@ export class Assertions {
531531
}
532532
});
533533

534-
this.throwsAsync = withSkip((...args) => {
534+
this.throwsAsync = withSkip(async (...args) => {
535535
let [thrower, expectations, message] = args;
536536

537537
if (!checkMessage('throwsAsync', message)) {
538-
return Promise.resolve();
538+
return null;
539539
}
540540

541541
if (typeof thrower !== 'function' && !isPromise(thrower)) {
@@ -545,14 +545,14 @@ export class Assertions {
545545
message: '`t.throwsAsync()` must be called with a function or promise',
546546
values: [formatWithLabel('Called with:', thrower)]
547547
}));
548-
return Promise.resolve();
548+
return null;
549549
}
550550

551551
try {
552552
expectations = validateExpectations('throwsAsync', expectations, args.length, experiments);
553553
} catch (error) {
554554
fail(error);
555-
return Promise.resolve();
555+
return null;
556556
}
557557

558558
const handlePromise = async (promise, wasReturned) => {
@@ -583,6 +583,7 @@ export class Assertions {
583583
return await intermediate;
584584
} catch {
585585
// Don't reject the returned promise, even if the assertion fails.
586+
return null;
586587
}
587588
};
588589

@@ -605,7 +606,7 @@ export class Assertions {
605606
actualStack: actual.stack,
606607
values: [formatWithLabel('Function threw synchronously. Use `t.throws()` instead:', actual)]
607608
}));
608-
return Promise.resolve();
609+
return null;
609610
}
610611

611612
if (isPromise(retval)) {
@@ -617,7 +618,7 @@ export class Assertions {
617618
message,
618619
values: [formatWithLabel('Function returned:', retval)]
619620
}));
620-
return Promise.resolve();
621+
return null;
621622
});
622623

623624
this.notThrows = withSkip((fn, message) => {

test-d/throws.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ class CustomError extends Error {
1212
}
1313

1414
test('throws', t => {
15-
expectType<Error>(t.throws(() => {}));
16-
const error2: CustomError = t.throws(() => {});
17-
expectType<CustomError>(error2);
18-
expectType<CustomError>(t.throws<CustomError>(() => {}));
15+
expectType<Error | null>(t.throws(() => {}));
16+
const error2: CustomError | null = t.throws(() => {});
17+
expectType<CustomError | null>(error2);
18+
expectType<CustomError | null>(t.throws<CustomError>(() => {}));
1919
});
2020

2121
test('throwsAsync', async t => {
22-
expectType<Error>(await t.throwsAsync(Promise.reject()));
23-
expectType<CustomError>(await t.throwsAsync<CustomError>(Promise.reject()));
22+
expectType<Error | null>(await t.throwsAsync(async () => {}));
23+
expectType<CustomError | null>(await t.throwsAsync<CustomError>(async () => {}));
24+
expectType<Error | null>(await t.throwsAsync(Promise.reject()));
25+
expectType<CustomError | null>(await t.throwsAsync<CustomError>(Promise.reject()));
2426
});

test-tap/assert.js

+20-8
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ function failsWith(t, fn, subset, {expectBoolean = true} = {}) {
112112
}
113113

114114
function throwsAsyncFails(t, fn, subset) {
115+
return add(() => {
116+
lastFailure = null;
117+
return fn().then(retval => {
118+
t.equal(retval, null);
119+
assertFailure(t, subset);
120+
});
121+
});
122+
}
123+
124+
function notThrowsAsyncFails(t, fn, subset) {
115125
return add(() => {
116126
lastFailure = null;
117127
return fn().then(retval => {
@@ -160,6 +170,8 @@ function throwsAsyncPasses(t, fn) {
160170
});
161171
}
162172

173+
const notThrowsAsyncPasses = throwsAsyncPasses;
174+
163175
test('.pass()', t => {
164176
passes(t, () => {
165177
return assertions.pass();
@@ -1521,31 +1533,31 @@ test('.notThrows()', gather(t => {
15211533

15221534
test('.notThrowsAsync()', gather(t => {
15231535
// Passes because the promise is resolved
1524-
throwsAsyncPasses(t, () => assertions.notThrowsAsync(Promise.resolve()));
1536+
notThrowsAsyncPasses(t, () => assertions.notThrowsAsync(Promise.resolve()));
15251537

1526-
throwsAsyncPasses(t, () => {
1538+
notThrowsAsyncPasses(t, () => {
15271539
return assertions.notThrowsAsync(Promise.resolve());
15281540
});
15291541

15301542
// Fails because the promise is rejected
1531-
throwsAsyncFails(t, () => assertions.notThrowsAsync(Promise.reject(new Error())), {
1543+
notThrowsAsyncFails(t, () => assertions.notThrowsAsync(Promise.reject(new Error())), {
15321544
assertion: 'notThrowsAsync',
15331545
message: '',
15341546
values: [{label: 'Promise rejected with:', formatted: /Error/}]
15351547
});
15361548

15371549
// Passes because the function returned a resolved promise
1538-
throwsAsyncPasses(t, () => assertions.notThrowsAsync(() => Promise.resolve()));
1550+
notThrowsAsyncPasses(t, () => assertions.notThrowsAsync(() => Promise.resolve()));
15391551

15401552
// Fails because the function returned a rejected promise
1541-
throwsAsyncFails(t, () => assertions.notThrowsAsync(() => Promise.reject(new Error())), {
1553+
notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => Promise.reject(new Error())), {
15421554
assertion: 'notThrowsAsync',
15431555
message: '',
15441556
values: [{label: 'Returned promise rejected with:', formatted: /Error/}]
15451557
});
15461558

15471559
// Fails because the function throws synchronously
1548-
throwsAsyncFails(t, () => assertions.notThrowsAsync(() => {
1560+
notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => {
15491561
throw new Error('sync');
15501562
}, 'message'), {
15511563
assertion: 'notThrowsAsync',
@@ -1556,15 +1568,15 @@ test('.notThrowsAsync()', gather(t => {
15561568
});
15571569

15581570
// Fails because the function did not return a promise
1559-
throwsAsyncFails(t, () => assertions.notThrowsAsync(() => {}, 'message'), {
1571+
notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => {}, 'message'), {
15601572
assertion: 'notThrowsAsync',
15611573
message: 'message',
15621574
values: [
15631575
{label: 'Function did not return a promise. Use `t.notThrows()` instead:', formatted: /undefined/}
15641576
]
15651577
});
15661578

1567-
throwsAsyncFails(t, () => assertions.notThrowsAsync(Promise.resolve(), null), {
1579+
notThrowsAsyncFails(t, () => assertions.notThrowsAsync(Promise.resolve(), null), {
15681580
assertion: 'notThrowsAsync',
15691581
improperUsage: true,
15701582
message: 'The assertion message must be a string',

types/assertions.d.ts

+7-18
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,9 @@ export interface SnapshotAssertion {
279279
export interface ThrowsAssertion {
280280
/**
281281
* Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
282-
* The error must satisfy all expectations.
282+
* The error must satisfy all expectations. Returns null when the assertion fails.
283283
*/
284-
<ThrownError extends Error>(fn: () => any, expectations?: ThrowsExpectation | null, message?: string): ThrownError;
284+
<ThrownError extends Error>(fn: () => any, expectations?: ThrowsExpectation | null, message?: string): ThrownError | null;
285285

286286
/** Skip this assertion. */
287287
skip(fn: () => any, expectations?: any, message?: string): void;
@@ -290,27 +290,16 @@ export interface ThrowsAssertion {
290290
export interface ThrowsAsyncAssertion {
291291
/**
292292
* Assert that the async function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error
293-
* value. You must await the result.
293+
* value. Returns null when the assertion fails. You must await the result. The error must satisfy all expectations.
294294
*/
295-
<ThrownError extends Error>(fn: () => PromiseLike<any>, expectations?: null, message?: string): Promise<ThrownError>;
296-
297-
/**
298-
* Assert that the async function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error
299-
* value. You must await the result. The error must satisfy all expectations.
300-
*/
301-
<ThrownError extends Error>(fn: () => PromiseLike<any>, expectations: ThrowsExpectation, message?: string): Promise<ThrownError>;
302-
303-
/**
304-
* Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
305-
* rejection reason. You must await the result.
306-
*/
307-
<ThrownError extends Error>(promise: PromiseLike<any>, expectations?: null, message?: string): Promise<ThrownError>; // eslint-disable-line @typescript-eslint/unified-signatures
295+
<ThrownError extends Error>(fn: () => PromiseLike<any>, expectations?: ThrowsExpectation | null, message?: string): Promise<ThrownError | null>;
308296

309297
/**
310298
* Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
311-
* rejection reason. You must await the result. The error must satisfy all expectations.
299+
* rejection reason. Returns null when the assertion fails. You must await the result. The error must satisfy all
300+
* expectations.
312301
*/
313-
<ThrownError extends Error>(promise: PromiseLike<any>, expectations: ThrowsExpectation, message?: string): Promise<ThrownError>; // eslint-disable-line @typescript-eslint/unified-signatures
302+
<ThrownError extends Error>(promise: PromiseLike<any>, expectations?: ThrowsExpectation | null, message?: string): Promise<ThrownError | null>; // eslint-disable-line @typescript-eslint/unified-signatures
314303

315304
/** Skip this assertion. */
316305
skip(thrower: any, expectations?: any, message?: string): void;

0 commit comments

Comments
 (0)