diff --git a/e2e/__tests__/toThrowErrorMatchingSnapshot.test.ts b/e2e/__tests__/toThrowErrorMatchingSnapshot.test.ts index 0137be8037a5..a33c79689a82 100644 --- a/e2e/__tests__/toThrowErrorMatchingSnapshot.test.ts +++ b/e2e/__tests__/toThrowErrorMatchingSnapshot.test.ts @@ -45,6 +45,24 @@ test("throws the error if tested function didn't throw error", () => { writeFiles(TESTS_DIR, {[filename]: template()}); const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false', filename]); expect(stderr).toMatch('Received function did not throw'); + expect(stderr).toMatch('Returned: undefined'); + expect(exitCode).toBe(1); + } +}); + +test("reports stringified returned value if tested function didn't throw error", () => { + const filename = 'throws-if-tested-function-did-not-throw.test.js'; + const template = + makeTemplate(`test('throws the error if tested function did not throw error', () => { + expect(() => { return { foo: 1, bar: { baz: "2", } };}).toThrowErrorMatchingSnapshot(); + }); + `); + + { + writeFiles(TESTS_DIR, {[filename]: template()}); + const {stderr, exitCode} = runJest(DIR, ['-w=1', '--ci=false', filename]); + expect(stderr).toMatch('Received function did not throw'); + expect(stderr).toMatch('Returned: {"bar": {"baz": "2"}, "foo": 1}'); expect(exitCode).toBe(1); } }); diff --git a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.ts.snap b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.ts.snap index de7b0318643b..92de70c8a335 100644 --- a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.ts.snap +++ b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.ts.snap @@ -92,6 +92,7 @@ exports[`toThrow error class did not throw at all 1`] = ` Expected constructor: Err Received function did not throw +Returned: undefined `; exports[`toThrow error class threw, but class did not match (error) 1`] = ` @@ -230,6 +231,7 @@ exports[`toThrow regexp did not throw at all 1`] = ` Expected pattern: /apple/ Received function did not throw +Returned: undefined `; exports[`toThrow regexp threw, but message did not match (error) 1`] = ` @@ -271,7 +273,8 @@ exports[`toThrow substring did not throw at all 1`] = ` Expected substring: "apple" -Received function did not throw +Received function did not throw +Returned: undefined `; exports[`toThrow substring threw, but message did not match (error) 1`] = ` diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.ts b/packages/expect/src/__tests__/toThrowMatchers.test.ts index 2a7d19f68da8..c1709cba2b48 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.ts +++ b/packages/expect/src/__tests__/toThrowMatchers.test.ts @@ -50,6 +50,10 @@ describe('toThrow', () => { ).toThrowErrorMatchingSnapshot(); }); + test('did not throw with a promise', async () => { + await jestExpect(Promise.resolve('banana')).resolves.not.toThrow('apple') + }); + test('threw, but message did not match (error)', () => { expect(() => { jestExpect(() => { @@ -108,6 +112,10 @@ describe('toThrow', () => { ).toThrowErrorMatchingSnapshot(); }); + test('did not throw at all with a promise', async () => { + await jestExpect(Promise.resolve('banana')).resolves.not.toThrow(/apple/) + }); + test('threw, but message did not match (error)', () => { expect(() => { jestExpect(() => { @@ -187,6 +195,10 @@ describe('toThrow', () => { ).toThrowErrorMatchingSnapshot(); }); + test('did not throw at all with a promise', async () => { + await jestExpect(Promise.resolve()).resolves.not.toThrow(Err) + }); + test('threw, but class did not match (error)', () => { expect(() => { jestExpect(() => { diff --git a/packages/expect/src/toThrowMatchers.ts b/packages/expect/src/toThrowMatchers.ts index 46fd5c6c4286..d63ea3439cc7 100644 --- a/packages/expect/src/toThrowMatchers.ts +++ b/packages/expect/src/toThrowMatchers.ts @@ -17,6 +17,7 @@ import { printExpected, printReceived, printWithType, + stringify, } from 'jest-matcher-utils'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import { @@ -82,13 +83,14 @@ export const createMatcher = ( }; let thrown = null; + let returnedValueOnNotThrow; if (fromPromise && isError(received)) { thrown = getThrown(received); } else { if (typeof received === 'function') { try { - received(); + returnedValueOnNotThrow = received(); } catch (error) { thrown = getThrown(error); } @@ -107,20 +109,61 @@ export const createMatcher = ( } if (expected === undefined) { - return toThrow(matcherName, options, thrown); + return toThrow( + matcherName, + options, + thrown, + received, + returnedValueOnNotThrow, + ); } else if (typeof expected === 'function') { - return toThrowExpectedClass(matcherName, options, thrown, expected); + return toThrowExpectedClass( + matcherName, + options, + thrown, + expected, + received, + returnedValueOnNotThrow, + ); } else if (typeof expected === 'string') { - return toThrowExpectedString(matcherName, options, thrown, expected); + return toThrowExpectedString( + matcherName, + options, + thrown, + expected, + received, + returnedValueOnNotThrow, + ); } else if (expected !== null && typeof expected.test === 'function') { - return toThrowExpectedRegExp(matcherName, options, thrown, expected); + return toThrowExpectedRegExp( + matcherName, + options, + thrown, + expected, + received, + returnedValueOnNotThrow, + ); } else if ( expected !== null && typeof expected.asymmetricMatch === 'function' ) { - return toThrowExpectedAsymmetric(matcherName, options, thrown, expected); + return toThrowExpectedAsymmetric( + matcherName, + options, + thrown, + expected, + received, + returnedValueOnNotThrow, + ); } else if (expected !== null && typeof expected === 'object') { - return toThrowExpectedObject(matcherName, options, thrown, expected); + return toThrowExpectedObject( + matcherName, + options, + thrown, + expected, + received, + returnedValueOnNotThrow, + ); } else { throw new Error( matcherErrorMessage( @@ -143,6 +186,8 @@ const toThrowExpectedRegExp = ( options: MatcherHintOptions, thrown: Thrown | null, expected: RegExp, + received: unknown, + returnedValueOnNotThrow: unknown, ): SyncExpectationResult => { const pass = thrown !== null && expected.test(thrown.message); @@ -166,7 +211,11 @@ const toThrowExpectedRegExp = ( '\n\n' + formatExpected('Expected pattern: ', expected) + (thrown === null - ? `\n${DID_NOT_THROW}` + ? `\n${DID_NOT_THROW}${ + typeof received === 'function' + ? `\nReturned: ${stringify(returnedValueOnNotThrow)}` + : '' + }` : thrown.hasMessage ? formatReceived('Received message: ', thrown, 'message') + formatStack(thrown) @@ -184,6 +233,8 @@ const toThrowExpectedAsymmetric = ( options: MatcherHintOptions, thrown: Thrown | null, expected: AsymmetricMatcher, + received: unknown, + returnedValueOnNotThrow: unknown, ): SyncExpectationResult => { const pass = thrown !== null && expected.asymmetricMatch(thrown.value); @@ -206,7 +257,11 @@ const toThrowExpectedAsymmetric = ( formatExpected('Expected asymmetric matcher: ', expected) + '\n' + (thrown === null - ? DID_NOT_THROW + ? `${DID_NOT_THROW}${ + typeof received === 'function' + ? `\nReturned: ${stringify(returnedValueOnNotThrow)}` + : '' + }` : thrown.hasMessage ? formatReceived('Received name: ', thrown, 'name') + formatReceived('Received message: ', thrown, 'message') + @@ -221,6 +276,8 @@ const toThrowExpectedObject = ( options: MatcherHintOptions, thrown: Thrown | null, expected: Error, + received: unknown, + returnedValueOnNotThrow: unknown, ): SyncExpectationResult => { const expectedMessageAndCause = createMessageAndCause(expected); const thrownMessageAndCause = @@ -260,7 +317,11 @@ const toThrowExpectedObject = ( expectedMessageAndCause, ) + '\n' + - DID_NOT_THROW + `${DID_NOT_THROW}${ + typeof received === 'function' + ? `\nReturned: ${stringify(returnedValueOnNotThrow)}` + : '' + }` : thrown.hasMessage ? // eslint-disable-next-line prefer-template printDiffOrStringify( @@ -285,6 +346,8 @@ const toThrowExpectedClass = ( options: MatcherHintOptions, thrown: Thrown | null, expected: Function, + received: unknown, + returnedValueOnNotThrow: unknown, ): SyncExpectationResult => { const pass = thrown !== null && thrown.value instanceof expected; @@ -315,7 +378,11 @@ const toThrowExpectedClass = ( '\n\n' + printExpectedConstructorName('Expected constructor', expected) + (thrown === null - ? `\n${DID_NOT_THROW}` + ? `\n${DID_NOT_THROW}${ + typeof received === 'function' + ? `\nReturned: ${stringify(returnedValueOnNotThrow)}` + : '' + }` : `${ thrown.value != null && typeof thrown.value.constructor === 'function' @@ -339,6 +406,8 @@ const toThrowExpectedString = ( options: MatcherHintOptions, thrown: Thrown | null, expected: string, + received: unknown, + returnedValueOnNotThrow: unknown, ): SyncExpectationResult => { const pass = thrown !== null && thrown.message.includes(expected); @@ -362,7 +431,11 @@ const toThrowExpectedString = ( '\n\n' + formatExpected('Expected substring: ', expected) + (thrown === null - ? `\n${DID_NOT_THROW}` + ? `\n${DID_NOT_THROW} ${ + typeof received === 'function' + ? `\nReturned: ${stringify(returnedValueOnNotThrow)}` + : '' + }` : thrown.hasMessage ? formatReceived('Received message: ', thrown, 'message') + formatStack(thrown) @@ -375,6 +448,8 @@ const toThrow = ( matcherName: string, options: MatcherHintOptions, thrown: Thrown | null, + received: unknown, + returnedValueOnNotThrow: unknown, ): SyncExpectationResult => { const pass = thrown !== null; @@ -392,7 +467,10 @@ const toThrow = ( // eslint-disable-next-line prefer-template matcherHint(matcherName, undefined, '', options) + '\n\n' + - DID_NOT_THROW; + DID_NOT_THROW + + (typeof received === 'function' + ? `\nReturned: ${stringify(returnedValueOnNotThrow)}` + : ''); return {message, pass}; }; diff --git a/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap b/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap index 1b1aea2e1561..2b7f6beb5b69 100644 --- a/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap +++ b/packages/jest-snapshot/src/__tests__/__snapshots__/printSnapshot.test.ts.snap @@ -120,6 +120,7 @@ exports[`other error toThrowErrorMatchingSnapshot Received function did not thro expect(received).toThrowErrorMatchingSnapshot() Received function did not throw +Returned: undefined `; exports[`pass false toMatchInlineSnapshot with properties equals false with snapshot 1`] = ` diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index 99f1dbf659df..36ec3666c28d 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -509,12 +509,13 @@ const _toThrowErrorMatchingSnapshot = ( } let error; + let returnedValueOnNotThrow; if (fromPromise) { error = received; } else { try { - received(); + returnedValueOnNotThrow = received(); } catch (receivedError) { error = receivedError; } @@ -523,7 +524,11 @@ const _toThrowErrorMatchingSnapshot = ( if (error === undefined) { // Because the received value is a function, this is not a matcher error. throw new Error( - `${matcherHintFromConfig(config, false)}\n\n${DID_NOT_THROW}`, + `${matcherHintFromConfig(config, false)}\n\n${DID_NOT_THROW}${ + typeof received === 'function' + ? `\nReturned: ${stringify(returnedValueOnNotThrow)}` + : '' + }`, ); }