Skip to content

Commit d451174

Browse files
committed
Make t.throws() and t.notThrows() accept async function as parameter.
Refs: #1371
1 parent 7be7fe8 commit d451174

File tree

5 files changed

+101
-10
lines changed

5 files changed

+101
-10
lines changed

index.js.flow

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ type AssertContext = {
5555
deepEqual<U>(value: U, expected: U, message?: string): void;
5656
// Assert that value is not deep equal to expected.
5757
notDeepEqual<U>(value: U, expected: U, message?: string): void;
58-
// Assert that function throws an error or promise rejects.
58+
// Assert that the promise rejects, or the function throws or returns a rejected promise.
5959
// @param error Can be a constructor, regex, error message or validation function.
6060
throws: {
6161
(value: PromiseLike<mixed>, error?: ErrorValidator, message?: string): Promise<any>;
6262
(value: () => mixed, error?: ErrorValidator, message?: string): any;
6363
};
64-
// Assert that function doesn't throw an error or promise resolves.
64+
// Assert that the promise resolves, or the function doesn't throws or returns a resolved promise.
6565
notThrows: {
6666
(value: PromiseLike<mixed>, message?: string): Promise<void>;
6767
(value: () => mixed, message?: string): void;

lib/assert.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,14 @@ function wrapAssertions(callbacks) {
198198
coreAssertThrowsErrorArg = err;
199199
}
200200

201+
let maybePromise;
201202
const test = (fn, stack) => {
202203
let actual;
203204
let threw = false;
204205
try {
205206
coreAssert.throws(() => {
206207
try {
207-
fn();
208+
maybePromise = fn();
208209
} catch (err) {
209210
actual = err;
210211
threw = true;
@@ -224,7 +225,7 @@ function wrapAssertions(callbacks) {
224225
}
225226
};
226227

227-
if (promise) {
228+
const handlePromise = promise => {
228229
// Record stack before it gets lost in the promise chain.
229230
const stack = getStack();
230231
const intermediate = promise.then(value => {
@@ -238,13 +239,25 @@ function wrapAssertions(callbacks) {
238239
pending(this, intermediate);
239240
// Don't reject the returned promise, even if the assertion fails.
240241
return intermediate.catch(noop);
242+
};
243+
244+
if (promise) {
245+
return handlePromise(promise);
241246
}
242247

243248
try {
244249
const retval = test(fn);
245250
pass(this);
246251
return retval;
247252
} catch (err) {
253+
if (maybePromise) {
254+
if (isPromise(maybePromise)) {
255+
return handlePromise(maybePromise);
256+
}
257+
if (isObservable(maybePromise)) {
258+
return handlePromise(observableToPromise(maybePromise));
259+
}
260+
}
248261
fail(this, err);
249262
}
250263
},
@@ -265,9 +278,12 @@ function wrapAssertions(callbacks) {
265278
return;
266279
}
267280

281+
let maybePromise;
268282
const test = (fn, stack) => {
269283
try {
270-
coreAssert.doesNotThrow(fn);
284+
coreAssert.doesNotThrow(() => {
285+
maybePromise = fn();
286+
});
271287
} catch (err) {
272288
throw new AssertionError({
273289
assertion: 'notThrows',
@@ -278,17 +294,29 @@ function wrapAssertions(callbacks) {
278294
}
279295
};
280296

281-
if (promise) {
297+
const handlePromise = promise => {
282298
// Record stack before it gets lost in the promise chain.
283299
const stack = getStack();
284300
const intermediate = promise.then(noop, reason => test(makeRethrow(reason), stack));
285301
pending(this, intermediate);
286302
// Don't reject the returned promise, even if the assertion fails.
287303
return intermediate.catch(noop);
304+
};
305+
306+
if (promise) {
307+
return handlePromise(promise);
288308
}
289309

290310
try {
291311
test(fn);
312+
if (maybePromise) {
313+
if (isPromise(maybePromise)) {
314+
return handlePromise(maybePromise);
315+
}
316+
if (isObservable(maybePromise)) {
317+
return handlePromise(observableToPromise(maybePromise));
318+
}
319+
}
292320
pass(this);
293321
} catch (err) {
294322
fail(this, err);

readme.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ Assert that `value` is not deeply equal to `expected`. The inverse of `.deepEqua
913913

914914
### `.throws(function|promise, [error, [message]])`
915915

916-
Assert that `function` throws an error, or `promise` rejects with an error.
916+
Assert that `function` throws an error, `promise` rejects with an error, or `function` returns a rejected `promise`.
917917

918918
`error` can be an error constructor, error message, regex matched against the error message, or validation function.
919919

@@ -952,9 +952,21 @@ test('rejects', async t => {
952952
});
953953
```
954954

955+
When testing an `async function` you must also wait for the assertion to complete:
956+
957+
```js
958+
test('throws', async t => {
959+
const error = await t.throws(async () => {
960+
throw new TypeError('🦄');
961+
}, TypeError);
962+
963+
t.is(error.message, '🦄');
964+
});
965+
```
966+
955967
### `.notThrows(function|promise, [message])`
956968

957-
Assert that `function` does not throw an error or that `promise` does not reject with an error.
969+
Assert that `function` does not throw an error, `promise` does not reject with an error, or `function` returns a promise that does not reject with an error.
958970

959971
Like the `.throws()` assertion, when testing a promise you must wait for the assertion to complete:
960972

test/assert.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const stripAnsi = require('strip-ansi');
66
const React = require('react');
77
const renderer = require('react-test-renderer');
88
const test = require('tap').test;
9+
const Observable = require('zen-observable');
910
const assert = require('../lib/assert');
1011
const snapshotManager = require('../lib/snapshot-manager');
1112
const Test = require('../lib/test');
@@ -655,7 +656,11 @@ test('.throws()', t => {
655656
});
656657
});
657658

658-
t.end();
659+
return eventuallyFailsWith(t, assertions.throws(() => Promise.resolve('foo')), {
660+
assertion: 'throws',
661+
message: 'Expected promise to be rejected, but it was resolved instead',
662+
values: [{label: 'Resolved with:', formatted: /'foo'/}]
663+
});
659664
});
660665

661666
test('.throws() returns the thrown error', t => {
@@ -678,6 +683,28 @@ test('.throws() returns the rejection reason of promise', t => {
678683
});
679684
});
680685

686+
test('.throws() returns the rejection reason of a promise returned by the function', t => {
687+
const expected = new Error();
688+
689+
return assertions.throws(() => {
690+
return Promise.reject(expected);
691+
}).then(actual => {
692+
t.is(actual, expected);
693+
t.end();
694+
});
695+
});
696+
697+
test('.throws() returns the error of an observable returned by the function', t => {
698+
const expected = new Error();
699+
700+
return assertions.throws(() => {
701+
return new Observable(observer => observer.error(expected));
702+
}).then(actual => {
703+
t.is(actual, expected);
704+
t.end();
705+
});
706+
});
707+
681708
test('.throws() fails if passed a bad value', t => {
682709
failsWith(t, () => {
683710
assertions.throws('not a function');
@@ -723,7 +750,13 @@ test('.notThrows()', t => {
723750
values: [{label: 'Threw:', formatted: /foo/}]
724751
});
725752

726-
t.end();
753+
return eventuallyFailsWith(t, assertions.notThrows(() => {
754+
return Promise.reject(new Error('foo'));
755+
}), {
756+
assertion: 'notThrows',
757+
message: '',
758+
values: [{label: 'Threw:', formatted: /foo/}]
759+
});
727760
});
728761

729762
test('.notThrows() returns undefined for a fulfilled promise', t => {
@@ -732,6 +765,22 @@ test('.notThrows() returns undefined for a fulfilled promise', t => {
732765
});
733766
});
734767

768+
test('.notThrows() returns undefined for a fulfilled promise returned by the function', t => {
769+
return assertions.notThrows(() => {
770+
return Promise.resolve(Symbol(''));
771+
}).then(actual => {
772+
t.is(actual, undefined);
773+
});
774+
});
775+
776+
test('.notThrows() returns undefined for an observable returned by the function', t => {
777+
return assertions.notThrows(() => {
778+
return Observable.of(Symbol(''));
779+
}).then(actual => {
780+
t.is(actual, undefined);
781+
});
782+
});
783+
735784
test('.notThrows() fails if passed a bad value', t => {
736785
failsWith(t, () => {
737786
assertions.notThrows('not a function');

types/base.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,13 @@ export interface AssertContext {
6767
* @param error Can be a constructor, regex, error message or validation function.
6868
*/
6969
throws(value: PromiseLike<any>, error?: ErrorValidator, message?: string): Promise<any>;
70+
throws(value: () => PromiseLike<any>, error?: ErrorValidator, message?: string): Promise<any>;
7071
throws(value: () => void, error?: ErrorValidator, message?: string): any;
7172
/**
7273
* Assert that function doesn't throw an error or promise resolves.
7374
*/
7475
notThrows(value: PromiseLike<any>, message?: string): Promise<void>;
76+
notThrows(value: () => PromiseLike<any>, message?: string): Promise<void>;
7577
notThrows(value: () => void, message?: string): void;
7678
/**
7779
* Assert that contents matches regex.

0 commit comments

Comments
 (0)