diff --git a/CHANGELOG.md b/CHANGELOG.md index 872870ca9577..ca91b9d179c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixes - `[jest-cli]` Refactor `-o` and `--coverage` combined ([#7611](https://github.com/facebook/jest/pull/7611)) +- `[expect]` Fix custom async matcher stack trace ([#7652](https://github.com/facebook/jest/pull/7652)) ### Chore & Maintenance diff --git a/e2e/__tests__/__snapshots__/customMatcherStackTrace.test.js.snap b/e2e/__tests__/__snapshots__/customMatcherStackTrace.test.js.snap index 8faa1ffd5ebd..15df98763079 100644 --- a/e2e/__tests__/__snapshots__/customMatcherStackTrace.test.js.snap +++ b/e2e/__tests__/__snapshots__/customMatcherStackTrace.test.js.snap @@ -1,7 +1,26 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`custom async matchers 1`] = ` +FAIL __tests__/asynchronous.test.js + ✕ showing the stack trace for an async matcher + + ● showing the stack trace for an async matcher + + We expect the stack trace and code fence for this matcher to be shown in the console. + + 9 | + 10 | test('showing the stack trace for an async matcher', async () => { + > 11 | await expect(true).toThrowCustomAsyncMatcherError(); + | ^ + 12 | }); + 13 | + 14 | async function toThrowCustomAsyncMatcherError() { + + at Object.toThrowCustomAsyncMatcherError (__tests__/asynchronous.test.js:11:22) +`; + exports[`works with custom matchers 1`] = ` -FAIL __tests__/customMatcher.test.js +FAIL __tests__/sync.test.js Custom matcher ✓ passes ✓ fails @@ -19,10 +38,10 @@ FAIL __tests__/customMatcher.test.js 47 | 48 | // This expecation fails due to an error we throw (intentionally) - at Error (__tests__/customMatcher.test.js:45:13) - at baz (__tests__/customMatcher.test.js:43:23) - at bar (__tests__/customMatcher.test.js:42:23) - at foo (__tests__/customMatcher.test.js:52:7) - at Object.callback (__tests__/customMatcher.test.js:11:18) - at Object.toCustomMatch (__tests__/customMatcher.test.js:53:8) + at Error (__tests__/sync.test.js:45:13) + at baz (__tests__/sync.test.js:43:23) + at bar (__tests__/sync.test.js:42:23) + at foo (__tests__/sync.test.js:52:7) + at Object.callback (__tests__/sync.test.js:11:18) + at Object.toCustomMatch (__tests__/sync.test.js:53:8) `; diff --git a/e2e/__tests__/__snapshots__/expectAsyncMatcher.test.js.snap b/e2e/__tests__/__snapshots__/expectAsyncMatcher.test.js.snap index 6ef5f68f3fca..b7d07fa98db9 100644 --- a/e2e/__tests__/__snapshots__/expectAsyncMatcher.test.js.snap +++ b/e2e/__tests__/__snapshots__/expectAsyncMatcher.test.js.snap @@ -10,22 +10,40 @@ FAIL __tests__/failure.test.js ● fail with expected non promise values Expected value to have length: + 2 + Received: + 1 + received.length: + 1 + + 11 | + 12 | it('fail with expected non promise values', () => + > 13 | expect([1]).toHaveLengthAsync(Promise.resolve(2))); + | ^ + 14 | + 15 | it('fail with expected non promise values and not', () => + 16 | expect([1, 2]).not.toHaveLengthAsync(Promise.resolve(2))); - 2 - Received: - 1 - received.length: - 1 + at Object.toHaveLengthAsync (__tests__/failure.test.js:13:15) ● fail with expected non promise values and not Expected value to not have length: + 2 + Received: + 1,2 + received.length: + 2 + + 14 | + 15 | it('fail with expected non promise values and not', () => + > 16 | expect([1, 2]).not.toHaveLengthAsync(Promise.resolve(2))); + | ^ + 17 | + 18 | it('fail with expected promise values', () => + 19 | expect(Promise.resolve([1])).resolves.toHaveLengthAsync(Promise.resolve(2))); - 2 - Received: - 1,2 - received.length: - 2 + at Object.toHaveLengthAsync (__tests__/failure.test.js:16:22) ● fail with expected promise values @@ -36,19 +54,15 @@ FAIL __tests__/failure.test.js received.length: 1 - 22 | - 23 | it('fail with expected promise values', async () => { - > 24 | await (expect(Promise.resolve([1])): any).resolves.toHaveLengthAsync( - | ^ - 25 | Promise.resolve(2) - 26 | ); - 27 | }); + 17 | + 18 | it('fail with expected promise values', () => + > 19 | expect(Promise.resolve([1])).resolves.toHaveLengthAsync(Promise.resolve(2))); + | ^ + 20 | + 21 | it('fail with expected promise values and not', () => + 22 | expect(Promise.resolve([1, 2])).resolves.not.toHaveLengthAsync( - at Object.toHaveLengthAsync (__tests__/failure.test.js:24:54) - at asyncGeneratorStep (__tests__/failure.test.js:11:103) - at _next (__tests__/failure.test.js:13:194) - at __tests__/failure.test.js:13:364 - at Object. (__tests__/failure.test.js:13:97) + at Object.toHaveLengthAsync (__tests__/failure.test.js:19:41) ● fail with expected promise values and not @@ -59,17 +73,13 @@ FAIL __tests__/failure.test.js received.length: 2 - 28 | - 29 | it('fail with expected promise values and not', async () => { - > 30 | await (expect(Promise.resolve([1, 2])).resolves.not: any).toHaveLengthAsync( - | ^ - 31 | Promise.resolve(2) - 32 | ); - 33 | }); - - at Object.toHaveLengthAsync (__tests__/failure.test.js:30:61) - at asyncGeneratorStep (__tests__/failure.test.js:11:103) - at _next (__tests__/failure.test.js:13:194) - at __tests__/failure.test.js:13:364 - at Object. (__tests__/failure.test.js:13:97) + 20 | + 21 | it('fail with expected promise values and not', () => + > 22 | expect(Promise.resolve([1, 2])).resolves.not.toHaveLengthAsync( + | ^ + 23 | Promise.resolve(2) + 24 | )); + 25 | + + at Object.toHaveLengthAsync (__tests__/failure.test.js:22:48) `; diff --git a/e2e/__tests__/customMatcherStackTrace.test.js b/e2e/__tests__/customMatcherStackTrace.test.js index c9b13df573e0..be338b2b7ee7 100644 --- a/e2e/__tests__/customMatcherStackTrace.test.js +++ b/e2e/__tests__/customMatcherStackTrace.test.js @@ -12,7 +12,7 @@ import {extractSummary} from '../Utils'; import {wrap} from 'jest-snapshot-serializer-raw'; test('works with custom matchers', () => { - const {stderr} = runJest('custom-matcher-stack-trace'); + const {stderr} = runJest('custom-matcher-stack-trace', ['sync.test.js']); let {rest} = extractSummary(stderr); @@ -23,3 +23,13 @@ test('works with custom matchers', () => { expect(wrap(rest)).toMatchSnapshot(); }); + +test('custom async matchers', () => { + const {stderr} = runJest('custom-matcher-stack-trace', [ + 'asynchronous.test.js', + ]); + + const {rest} = extractSummary(stderr); + + expect(wrap(rest)).toMatchSnapshot(); +}); diff --git a/e2e/custom-matcher-stack-trace/__tests__/asynchronous.test.js b/e2e/custom-matcher-stack-trace/__tests__/asynchronous.test.js new file mode 100644 index 000000000000..33061bffa53e --- /dev/null +++ b/e2e/custom-matcher-stack-trace/__tests__/asynchronous.test.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +expect.extend({toThrowCustomAsyncMatcherError}); + +test('showing the stack trace for an async matcher', async () => { + await expect(true).toThrowCustomAsyncMatcherError(); +}); + +async function toThrowCustomAsyncMatcherError() { + const message = () => + 'We expect the stack trace and code fence for this matcher to be shown in the console.'; + return {message, pass: false}; +} diff --git a/e2e/custom-matcher-stack-trace/__tests__/customMatcher.test.js b/e2e/custom-matcher-stack-trace/__tests__/sync.test.js similarity index 100% rename from e2e/custom-matcher-stack-trace/__tests__/customMatcher.test.js rename to e2e/custom-matcher-stack-trace/__tests__/sync.test.js diff --git a/e2e/expect-async-matcher/__tests__/failure.test.js b/e2e/expect-async-matcher/__tests__/failure.test.js index e10b1947c556..206948d90a9b 100644 --- a/e2e/expect-async-matcher/__tests__/failure.test.js +++ b/e2e/expect-async-matcher/__tests__/failure.test.js @@ -4,30 +4,21 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -'use strict'; import {toHaveLengthAsync} from '../matchers'; -expect.extend({ - toHaveLengthAsync, -}); +expect.extend({toHaveLengthAsync}); -it('fail with expected non promise values', async () => { - await (expect([1]): any).toHaveLengthAsync(Promise.resolve(2)); -}); +it('fail with expected non promise values', () => + expect([1]).toHaveLengthAsync(Promise.resolve(2))); -it('fail with expected non promise values and not', async () => { - await (expect([1, 2]): any).not.toHaveLengthAsync(Promise.resolve(2)); -}); +it('fail with expected non promise values and not', () => + expect([1, 2]).not.toHaveLengthAsync(Promise.resolve(2))); -it('fail with expected promise values', async () => { - await (expect(Promise.resolve([1])): any).resolves.toHaveLengthAsync( - Promise.resolve(2) - ); -}); +it('fail with expected promise values', () => + expect(Promise.resolve([1])).resolves.toHaveLengthAsync(Promise.resolve(2))); -it('fail with expected promise values and not', async () => { - await (expect(Promise.resolve([1, 2])).resolves.not: any).toHaveLengthAsync( +it('fail with expected promise values and not', () => + expect(Promise.resolve([1, 2])).resolves.not.toHaveLengthAsync( Promise.resolve(2) - ); -}); + )); diff --git a/e2e/expect-async-matcher/__tests__/success.test.js b/e2e/expect-async-matcher/__tests__/success.test.js index ac3a8a738b94..8f597b9788ea 100644 --- a/e2e/expect-async-matcher/__tests__/success.test.js +++ b/e2e/expect-async-matcher/__tests__/success.test.js @@ -4,30 +4,21 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -'use strict'; import {toHaveLengthAsync} from '../matchers'; -expect.extend({ - toHaveLengthAsync, -}); +expect.extend({toHaveLengthAsync}); -it('works with expected non promise values', async () => { - await (expect([1]): any).toHaveLengthAsync(Promise.resolve(1)); -}); +it('works with expected non promise values', () => + expect([1]).toHaveLengthAsync(Promise.resolve(1))); -it('works with expected non promise values and not', async () => { - await (expect([1, 2]): any).not.toHaveLengthAsync(Promise.resolve(1)); -}); +it('works with expected non promise values and not', () => + expect([1, 2]).not.toHaveLengthAsync(Promise.resolve(1))); -it('works with expected promise values', async () => { - await (expect(Promise.resolve([1])).resolves: any).toHaveLengthAsync( - Promise.resolve(1) - ); -}); +it('works with expected promise values', () => + expect(Promise.resolve([1])).resolves.toHaveLengthAsync(Promise.resolve(1))); -it('works with expected promise values and not', async () => { - await (expect(Promise.resolve([1, 2])).resolves.not: any).toHaveLengthAsync( +it('works with expected promise values and not', () => + expect(Promise.resolve([1, 2])).resolves.not.toHaveLengthAsync( Promise.resolve(1) - ); -}); + )); diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index 1c41fc8e683f..1f50866a4e67 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -251,7 +251,10 @@ const makeThrowingMatcher = ( utils, }; - const processResult = (result: SyncExpectationResult) => { + const processResult = ( + result: SyncExpectationResult, + asyncError?: JestAssertionError, + ) => { _validateResult(result); getState().assertionCalls++; @@ -264,6 +267,9 @@ const makeThrowingMatcher = ( if (err) { error = err; error.message = message; + } else if (asyncError) { + error = asyncError; + error.message = message; } else { error = new JestAssertionError(message); @@ -307,9 +313,13 @@ const makeThrowingMatcher = ( if (isPromise((potentialResult: any))) { const asyncResult = ((potentialResult: any): AsyncExpectationResult); + const asyncError = new JestAssertionError(); + if (Error.captureStackTrace) { + Error.captureStackTrace(asyncError, throwingMatcher); + } return asyncResult - .then(aResult => processResult(aResult)) + .then(aResult => processResult(aResult, asyncError)) .catch(error => handlError(error)); } else { const syncResult = ((potentialResult: any): SyncExpectationResult);