From 552a0a408baede12e9c708cc6e933f3e48b064ba Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 5 Jul 2019 15:11:39 -0400 Subject: [PATCH 1/6] expect: Improve report when mock-spy matcher fails, part 2 --- .../__snapshots__/spyMatchers.test.js.snap | 256 ++++++++++-------- packages/expect/src/spyMatchers.ts | 161 +++++------ 2 files changed, 218 insertions(+), 199 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap index 8d7d7584d4fc..6cc9f96b88ca 100644 --- a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap @@ -607,9 +607,10 @@ Expected has value: 555" `; exports[`toBeCalled .not passes when called 1`] = ` -"expect(jest.fn()).toBeCalled() +"expect(jest.fn()).toBeCalled() -Expected mock function to have been called, but it was not called." +Expected number of calls: >= 1 +Received number of calls: 0" `; exports[`toBeCalled fails with any argument passed 1`] = ` @@ -622,17 +623,21 @@ Expected has value: 555" `; exports[`toBeCalled includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toBeCalled() +"expect(named-mock).not.toBeCalled() -Expected mock function \\"named-mock\\" not to be called but it was called with: - []" +Expected number of calls: 0 +Received number of calls: 1 + +1: []" `; exports[`toBeCalled passes when called 1`] = ` -"expect(jest.fn()).not.toBeCalled() +"expect(jest.fn()).not.toBeCalled() + +Expected number of calls: 0 +Received number of calls: 1 -Expected mock function not to be called but it was called with: - []" +1: []" `; exports[`toBeCalled works only on spies or jest.fn 1`] = ` @@ -699,15 +704,17 @@ Expected has value: [Function anonymous]" `; exports[`toBeCalledTimes .not passes if function called less than expected times 1`] = ` -"expect(jest.fn()).toBeCalledTimes(2) +"expect(jest.fn()).toBeCalledTimes(expected) -Expected mock function to have been called two times, but it was called one time." +Expected number of calls: 2 +Received number of calls: 1" `; exports[`toBeCalledTimes .not passes if function called more than expected times 1`] = ` -"expect(jest.fn()).toBeCalledTimes(2) +"expect(jest.fn()).toBeCalledTimes(expected) -Expected mock function to have been called two times, but it was called three times." +Expected number of calls: 2 +Received number of calls: 3" `; exports[`toBeCalledTimes .not works only on spies or jest.fn 1`] = ` @@ -720,9 +727,10 @@ Received has value: [Function fn]" `; exports[`toBeCalledTimes includes the custom mock name in the error message 1`] = ` -"expect(named-mock).toBeCalledTimes(2) +"expect(named-mock).toBeCalledTimes(expected) -Expected mock function \\"named-mock\\" to have been called two times, but it was called one time." +Expected number of calls: 2 +Received number of calls: 1" `; exports[`toBeCalledTimes only accepts a number argument 1`] = ` @@ -780,9 +788,9 @@ Expected has value: [Function anonymous]" `; exports[`toBeCalledTimes passes if function called equal to expected times 1`] = ` -"expect(jest.fn()).not.toBeCalledTimes(2) +"expect(jest.fn()).not.toBeCalledTimes(expected) -Expected mock function not to be called two times, but it was called exactly two times." +Expected number of calls: not 2" `; exports[`toBeCalledWith includes the custom mock name in the error message 1`] = ` @@ -929,9 +937,10 @@ Expected has value: 555" `; exports[`toHaveBeenCalled .not passes when called 1`] = ` -"expect(jest.fn()).toHaveBeenCalled() +"expect(jest.fn()).toHaveBeenCalled() -Expected mock function to have been called, but it was not called." +Expected number of calls: >= 1 +Received number of calls: 0" `; exports[`toHaveBeenCalled fails with any argument passed 1`] = ` @@ -944,17 +953,21 @@ Expected has value: 555" `; exports[`toHaveBeenCalled includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toHaveBeenCalled() +"expect(named-mock).not.toHaveBeenCalled() + +Expected number of calls: 0 +Received number of calls: 1 -Expected mock function \\"named-mock\\" not to be called but it was called with: - []" +1: []" `; exports[`toHaveBeenCalled passes when called 1`] = ` -"expect(jest.fn()).not.toHaveBeenCalled() +"expect(jest.fn()).not.toHaveBeenCalled() -Expected mock function not to be called but it was called with: - []" +Expected number of calls: 0 +Received number of calls: 1 + +1: []" `; exports[`toHaveBeenCalled works only on spies or jest.fn 1`] = ` @@ -1021,15 +1034,17 @@ Expected has value: [Function anonymous]" `; exports[`toHaveBeenCalledTimes .not passes if function called less than expected times 1`] = ` -"expect(jest.fn()).toHaveBeenCalledTimes(2) +"expect(jest.fn()).toHaveBeenCalledTimes(expected) -Expected mock function to have been called two times, but it was called one time." +Expected number of calls: 2 +Received number of calls: 1" `; exports[`toHaveBeenCalledTimes .not passes if function called more than expected times 1`] = ` -"expect(jest.fn()).toHaveBeenCalledTimes(2) +"expect(jest.fn()).toHaveBeenCalledTimes(expected) -Expected mock function to have been called two times, but it was called three times." +Expected number of calls: 2 +Received number of calls: 3" `; exports[`toHaveBeenCalledTimes .not works only on spies or jest.fn 1`] = ` @@ -1042,9 +1057,10 @@ Received has value: [Function fn]" `; exports[`toHaveBeenCalledTimes includes the custom mock name in the error message 1`] = ` -"expect(named-mock).toHaveBeenCalledTimes(2) +"expect(named-mock).toHaveBeenCalledTimes(expected) -Expected mock function \\"named-mock\\" to have been called two times, but it was called one time." +Expected number of calls: 2 +Received number of calls: 1" `; exports[`toHaveBeenCalledTimes only accepts a number argument 1`] = ` @@ -1102,9 +1118,9 @@ Expected has value: [Function anonymous]" `; exports[`toHaveBeenCalledTimes passes if function called equal to expected times 1`] = ` -"expect(jest.fn()).not.toHaveBeenCalledTimes(2) +"expect(jest.fn()).not.toHaveBeenCalledTimes(expected) -Expected mock function not to be called two times, but it was called exactly two times." +Expected number of calls: not 2" `; exports[`toHaveBeenCalledWith includes the custom mock name in the error message 1`] = ` @@ -1848,21 +1864,24 @@ Expected has value: 555" `; exports[`toHaveReturned .not passes when a call throws undefined 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toHaveReturned .not passes when all calls throw 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toHaveReturned .not passes when not returned 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toHaveReturned .not works only on jest.fn 1`] = ` @@ -1884,39 +1903,47 @@ Expected has value: 555" `; exports[`toHaveReturned includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toHaveReturned() +"expect(named-mock).not.toHaveReturned() + +Expected number of returns: 0 +Received number of returns: 1 -Expected mock function \\"named-mock\\" not to have returned, but it returned: - 42" +1: 42" `; exports[`toHaveReturned incomplete recursive calls are handled properly 1`] = ` -"expect(jest.fn()).toHaveReturned() +"expect(jest.fn()).toHaveReturned() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toHaveReturned passes when at least one call does not throw 1`] = ` -"expect(jest.fn()).not.toHaveReturned() +"expect(jest.fn()).not.toHaveReturned() -Expected mock function not to have returned, but it returned: - 42 +Expected number of returns: 0 +Received number of returns: 2 - 42" +1: 42 +3: 42" `; exports[`toHaveReturned passes when returned 1`] = ` -"expect(jest.fn()).not.toHaveReturned() +"expect(jest.fn()).not.toHaveReturned() + +Expected number of returns: 0 +Received number of returns: 1 -Expected mock function not to have returned, but it returned: - 42" +1: 42" `; exports[`toHaveReturned passes when undefined is returned 1`] = ` -"expect(jest.fn()).not.toHaveReturned() +"expect(jest.fn()).not.toHaveReturned() -Expected mock function not to have returned, but it returned: - undefined" +Expected number of returns: 0 +Received number of returns: 1 + +1: undefined" `; exports[`toHaveReturnedTimes .not only accepts a number argument 1`] = ` @@ -1974,45 +2001,48 @@ Expected has value: [Function anonymous]" `; exports[`toHaveReturnedTimes .not passes if function called less than expected times 1`] = ` -"expect(jest.fn()).toHaveReturnedTimes(2) +"expect(jest.fn()).toHaveReturnedTimes(expected) -Expected mock function to have returned two times, but it returned one time." +Expected number of returns: 2 +Received number of returns: 1" `; exports[`toHaveReturnedTimes .not passes if function returned more than expected times 1`] = ` -"expect(jest.fn()).toHaveReturnedTimes(2) +"expect(jest.fn()).toHaveReturnedTimes(expected) -Expected mock function to have returned two times, but it returned three times." +Expected number of returns: 2 +Received number of returns: 3" `; exports[`toHaveReturnedTimes calls that return undefined are counted as returns 1`] = ` -"expect(jest.fn()).not.toHaveReturnedTimes(2) +"expect(jest.fn()).not.toHaveReturnedTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toHaveReturnedTimes calls that throw are not counted 1`] = ` -"expect(jest.fn()).not.toHaveReturnedTimes(2) +"expect(jest.fn()).not.toHaveReturnedTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toHaveReturnedTimes calls that throw undefined are not counted 1`] = ` -"expect(jest.fn()).not.toHaveReturnedTimes(2) +"expect(jest.fn()).not.toHaveReturnedTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toHaveReturnedTimes includes the custom mock name in the error message 1`] = ` -"expect(named-mock).toHaveReturnedTimes(1) +"expect(named-mock).toHaveReturnedTimes(expected) -Expected mock function \\"named-mock\\" to have returned one time, but it returned two times." +Expected number of returns: 1 +Received number of returns: 2" `; exports[`toHaveReturnedTimes incomplete recursive calls are handled properly 1`] = ` -"expect(jest.fn()).not.toHaveReturnedTimes(2) +"expect(jest.fn()).not.toHaveReturnedTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toHaveReturnedTimes only accepts a number argument 1`] = ` @@ -2070,9 +2100,9 @@ Expected has value: [Function anonymous]" `; exports[`toHaveReturnedTimes passes if function returned equal to expected times 1`] = ` -"expect(jest.fn()).not.toHaveReturnedTimes(2) +"expect(jest.fn()).not.toHaveReturnedTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toHaveReturnedTimes works only on spies or jest.fn 1`] = ` @@ -2243,21 +2273,24 @@ Expected has value: 555" `; exports[`toReturn .not passes when a call throws undefined 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toReturn .not passes when all calls throw 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toReturn .not passes when not returned 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toReturn .not works only on jest.fn 1`] = ` @@ -2279,39 +2312,47 @@ Expected has value: 555" `; exports[`toReturn includes the custom mock name in the error message 1`] = ` -"expect(named-mock).not.toReturn() +"expect(named-mock).not.toReturn() + +Expected number of returns: 0 +Received number of returns: 1 -Expected mock function \\"named-mock\\" not to have returned, but it returned: - 42" +1: 42" `; exports[`toReturn incomplete recursive calls are handled properly 1`] = ` -"expect(jest.fn()).toReturn() +"expect(jest.fn()).toReturn() -Expected mock function to have returned." +Expected number of returns: >= 1 +Received number of returns: 0" `; exports[`toReturn passes when at least one call does not throw 1`] = ` -"expect(jest.fn()).not.toReturn() +"expect(jest.fn()).not.toReturn() -Expected mock function not to have returned, but it returned: - 42 +Expected number of returns: 0 +Received number of returns: 2 - 42" +1: 42 +3: 42" `; exports[`toReturn passes when returned 1`] = ` -"expect(jest.fn()).not.toReturn() +"expect(jest.fn()).not.toReturn() -Expected mock function not to have returned, but it returned: - 42" +Expected number of returns: 0 +Received number of returns: 1 + +1: 42" `; exports[`toReturn passes when undefined is returned 1`] = ` -"expect(jest.fn()).not.toReturn() +"expect(jest.fn()).not.toReturn() -Expected mock function not to have returned, but it returned: - undefined" +Expected number of returns: 0 +Received number of returns: 1 + +1: undefined" `; exports[`toReturnTimes .not only accepts a number argument 1`] = ` @@ -2369,45 +2410,48 @@ Expected has value: [Function anonymous]" `; exports[`toReturnTimes .not passes if function called less than expected times 1`] = ` -"expect(jest.fn()).toReturnTimes(2) +"expect(jest.fn()).toReturnTimes(expected) -Expected mock function to have returned two times, but it returned one time." +Expected number of returns: 2 +Received number of returns: 1" `; exports[`toReturnTimes .not passes if function returned more than expected times 1`] = ` -"expect(jest.fn()).toReturnTimes(2) +"expect(jest.fn()).toReturnTimes(expected) -Expected mock function to have returned two times, but it returned three times." +Expected number of returns: 2 +Received number of returns: 3" `; exports[`toReturnTimes calls that return undefined are counted as returns 1`] = ` -"expect(jest.fn()).not.toReturnTimes(2) +"expect(jest.fn()).not.toReturnTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toReturnTimes calls that throw are not counted 1`] = ` -"expect(jest.fn()).not.toReturnTimes(2) +"expect(jest.fn()).not.toReturnTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toReturnTimes calls that throw undefined are not counted 1`] = ` -"expect(jest.fn()).not.toReturnTimes(2) +"expect(jest.fn()).not.toReturnTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toReturnTimes includes the custom mock name in the error message 1`] = ` -"expect(named-mock).toReturnTimes(1) +"expect(named-mock).toReturnTimes(expected) -Expected mock function \\"named-mock\\" to have returned one time, but it returned two times." +Expected number of returns: 1 +Received number of returns: 2" `; exports[`toReturnTimes incomplete recursive calls are handled properly 1`] = ` -"expect(jest.fn()).not.toReturnTimes(2) +"expect(jest.fn()).not.toReturnTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toReturnTimes only accepts a number argument 1`] = ` @@ -2465,9 +2509,9 @@ Expected has value: [Function anonymous]" `; exports[`toReturnTimes passes if function returned equal to expected times 1`] = ` -"expect(jest.fn()).not.toReturnTimes(2) +"expect(jest.fn()).not.toReturnTimes(expected) -Expected mock function not to have returned two times, but it returned exactly two times." +Expected number of returns: not 2" `; exports[`toReturnTimes works only on spies or jest.fn 1`] = ` diff --git a/packages/expect/src/spyMatchers.ts b/packages/expect/src/spyMatchers.ts index 6286dbc60b35..cffbd71b2a4e 100644 --- a/packages/expect/src/spyMatchers.ts +++ b/packages/expect/src/spyMatchers.ts @@ -10,11 +10,9 @@ import { diff, ensureExpectedIsNumber, ensureNoExpected, - EXPECTED_COLOR, matcherErrorMessage, matcherHint, MatcherHintOptions, - pluralize, printExpected, printReceived, printWithType, @@ -24,6 +22,7 @@ import {MatchersObject, MatcherState, SyncExpectationResult} from './types'; import {equals} from './jasmineUtils'; import {iterableEquality, partition, isOneline} from './utils'; +const PRINT_LIMIT = 3; const CALL_PRINT_LIMIT = 3; const RETURN_PRINT_LIMIT = 5; const LAST_CALL_PRINT_LIMIT = 1; @@ -39,16 +38,11 @@ const createToBeCalledMatcher = (matcherName: string) => isNot: this.isNot, promise: this.promise, }; - ensureNoExpected(expected, matcherName.slice(1), options); - ensureMock(received, matcherName.slice(1), expectedArgument, options); + ensureNoExpected(expected, matcherName, options); + ensureMock(received, matcherName, expectedArgument, options); const receivedIsSpy = isSpy(received); - const type = receivedIsSpy ? 'spy' : 'mock function'; const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - const identifier = - receivedIsSpy || receivedName === 'jest.fn()' - ? type - : `${type} "${receivedName}"`; const count = receivedIsSpy ? received.calls.count() : received.mock.calls.length; @@ -58,14 +52,24 @@ const createToBeCalledMatcher = (matcherName: string) => const pass = count > 0; const message = pass ? () => - matcherHint('.not' + matcherName, receivedName, '') + + matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + - `Expected ${identifier} not to be called ` + - formatReceivedCalls(calls, CALL_PRINT_LIMIT, {sameSentence: true}) + `Expected number of calls: ${printExpected(0)}\n` + + `Received number of calls: ${printReceived(count)}\n\n` + + calls + .reduce((lines: Array, args: any, i: number) => { + if (lines.length < PRINT_LIMIT) { + lines.push(`${i + 1}: ${printReceived(args)}`); + } + + return lines; + }, []) + .join('\n') : () => - matcherHint(matcherName, receivedName, '') + + matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + - `Expected ${identifier} to have been called, but it was not called.`; + `Expected number of calls: >= ${printExpected(1)}\n` + + `Received number of calls: ${printReceived(count)}`; return {message, pass}; }; @@ -81,33 +85,39 @@ const createToReturnMatcher = (matcherName: string) => isNot: this.isNot, promise: this.promise, }; - ensureNoExpected(expected, matcherName.slice(1), options); - ensureMock(received, matcherName.slice(1), expectedArgument, options); + ensureNoExpected(expected, matcherName, options); + ensureMock(received, matcherName, expectedArgument, options); const receivedName = received.getMockName(); - const identifier = - receivedName === 'jest.fn()' - ? 'mock function' - : `mock function "${receivedName}"`; - // List of return values that correspond only to calls that returned - const returnValues = received.mock.results - .filter((result: any) => result.type === 'return') - .map((result: any) => result.value); + // Count return values that correspond only to calls that returned + const count = received.mock.results.reduce( + (n: number, result: any) => (result.type === 'return' ? n + 1 : n), + 0, + ); - const count = returnValues.length; const pass = count > 0; const message = pass ? () => - matcherHint('.not' + matcherName, receivedName, '') + + matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + - `Expected ${identifier} not to have returned, but it returned:\n` + - ` ${getPrintedReturnValues(returnValues, RETURN_PRINT_LIMIT)}` + `Expected number of returns: ${printExpected(0)}\n` + + `Received number of returns: ${printReceived(count)}\n\n` + + received.mock.results + .reduce((lines: Array, result: any, i: number) => { + if (result.type === 'return' && lines.length < PRINT_LIMIT) { + lines.push(`${i + 1}: ${printReceived(result.value)}`); + } + + return lines; + }, []) + .join('\n') : () => - matcherHint(matcherName, receivedName, '') + + matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + - `Expected ${identifier} to have returned.`; + `Expected number of returns: >= ${printExpected(1)}\n` + + `Received number of returns: ${printReceived(count)}`; return {message, pass}; }; @@ -123,33 +133,27 @@ const createToBeCalledTimesMatcher = (matcherName: string) => isNot: this.isNot, promise: this.promise, }; - ensureExpectedIsNumber(expected, matcherName.slice(1), options); - ensureMock(received, matcherName.slice(1), expectedArgument, options); + ensureExpectedIsNumber(expected, matcherName, options); + ensureMock(received, matcherName, expectedArgument, options); const receivedIsSpy = isSpy(received); - const type = receivedIsSpy ? 'spy' : 'mock function'; const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - const identifier = - receivedIsSpy || receivedName === 'jest.fn()' - ? type - : `${type} "${receivedName}"`; const count = receivedIsSpy ? received.calls.count() : received.mock.calls.length; + const pass = count === expected; + const message = pass ? () => - matcherHint('.not' + matcherName, receivedName, String(expected)) + + matcherHint(matcherName, receivedName, expectedArgument, options) + `\n\n` + - `Expected ${identifier} not to be called ` + - `${EXPECTED_COLOR(pluralize('time', expected))}, but it was` + - ` called exactly ${RECEIVED_COLOR(pluralize('time', count))}.` + `Expected number of calls: not ${printExpected(expected)}` : () => - matcherHint(matcherName, receivedName, String(expected)) + + matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + - `Expected ${identifier} to have been called ` + - `${EXPECTED_COLOR(pluralize('time', expected))},` + - ` but it was called ${RECEIVED_COLOR(pluralize('time', count))}.`; + `Expected number of calls: ${printExpected(expected)}\n` + + `Received number of calls: ${printReceived(count)}`; return {message, pass}; }; @@ -165,36 +169,29 @@ const createToReturnTimesMatcher = (matcherName: string) => isNot: this.isNot, promise: this.promise, }; - ensureExpectedIsNumber(expected, matcherName.slice(1), options); - ensureMock(received, matcherName.slice(1), expectedArgument, options); + ensureExpectedIsNumber(expected, matcherName, options); + ensureMock(received, matcherName, expectedArgument, options); const receivedName = received.getMockName(); - const identifier = - receivedName === 'jest.fn()' - ? 'mock function' - : `mock function "${receivedName}"`; - // List of return results that correspond only to calls that returned - const returnResults = received.mock.results.filter( - (result: any) => result.type === 'return', + // Count return values that correspond only to calls that returned + const count = received.mock.results.reduce( + (n: number, result: any) => (result.type === 'return' ? n + 1 : n), + 0, ); - const count = returnResults.length; const pass = count === expected; const message = pass ? () => - matcherHint('.not' + matcherName, receivedName, String(expected)) + + matcherHint(matcherName, receivedName, expectedArgument, options) + `\n\n` + - `Expected ${identifier} not to have returned ` + - `${EXPECTED_COLOR(pluralize('time', expected))}, but it` + - ` returned exactly ${RECEIVED_COLOR(pluralize('time', count))}.` + `Expected number of returns: not ${printExpected(expected)}` : () => - matcherHint(matcherName, receivedName, String(expected)) + + matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + - `Expected ${identifier} to have returned ` + - `${EXPECTED_COLOR(pluralize('time', expected))},` + - ` but it returned ${RECEIVED_COLOR(pluralize('time', count))}.`; + `Expected number of returns: ${printExpected(expected)}\n` + + `Received number of returns: ${printReceived(count)}`; return {message, pass}; }; @@ -520,11 +517,11 @@ const spyMatchers: MatchersObject = { lastReturnedWith: createLastReturnedMatcher('.lastReturnedWith'), nthCalledWith: createNthCalledWithMatcher('.nthCalledWith'), nthReturnedWith: createNthReturnedWithMatcher('.nthReturnedWith'), - toBeCalled: createToBeCalledMatcher('.toBeCalled'), - toBeCalledTimes: createToBeCalledTimesMatcher('.toBeCalledTimes'), + toBeCalled: createToBeCalledMatcher('toBeCalled'), + toBeCalledTimes: createToBeCalledTimesMatcher('toBeCalledTimes'), toBeCalledWith: createToBeCalledWithMatcher('.toBeCalledWith'), - toHaveBeenCalled: createToBeCalledMatcher('.toHaveBeenCalled'), - toHaveBeenCalledTimes: createToBeCalledTimesMatcher('.toHaveBeenCalledTimes'), + toHaveBeenCalled: createToBeCalledMatcher('toHaveBeenCalled'), + toHaveBeenCalledTimes: createToBeCalledTimesMatcher('toHaveBeenCalledTimes'), toHaveBeenCalledWith: createToBeCalledWithMatcher('.toHaveBeenCalledWith'), toHaveBeenLastCalledWith: createLastCalledWithMatcher( '.toHaveBeenLastCalledWith', @@ -534,11 +531,11 @@ const spyMatchers: MatchersObject = { ), toHaveLastReturnedWith: createLastReturnedMatcher('.toHaveLastReturnedWith'), toHaveNthReturnedWith: createNthReturnedWithMatcher('.toHaveNthReturnedWith'), - toHaveReturned: createToReturnMatcher('.toHaveReturned'), - toHaveReturnedTimes: createToReturnTimesMatcher('.toHaveReturnedTimes'), + toHaveReturned: createToReturnMatcher('toHaveReturned'), + toHaveReturnedTimes: createToReturnTimesMatcher('toHaveReturnedTimes'), toHaveReturnedWith: createToReturnWithMatcher('.toHaveReturnedWith'), - toReturn: createToReturnMatcher('.toReturn'), - toReturnTimes: createToReturnTimesMatcher('.toReturnTimes'), + toReturn: createToReturnMatcher('toReturn'), + toReturnTimes: createToReturnTimesMatcher('toReturnTimes'), toReturnWith: createToReturnWithMatcher('.toReturnWith'), }; @@ -595,28 +592,6 @@ const getPrintedReturnValues = (calls: Array, limit: number): string => { return result.join('\n\n '); }; -const formatReceivedCalls = ( - calls: Array, - limit: number, - options: any, -) => { - if (calls.length) { - const but = options && options.sameSentence ? 'but' : 'But'; - const count = calls.length - limit; - const printedCalls = getPrintedCalls(calls, limit, ', ', printReceived); - return ( - `${but} it was called ` + - `with:\n ` + - printedCalls + - (count > 0 - ? '\nand ' + RECEIVED_COLOR(pluralize('more call', count)) + '.' - : '') - ); - } else { - return `But it was ${RECEIVED_COLOR('not called')}.`; - } -}; - const formatMismatchedCalls = ( calls: Array, expected: any, From 87213ea0f9a7f194a7ee693d5499f5d45ad18e8d Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Fri, 5 Jul 2019 19:33:12 -0400 Subject: [PATCH 2/6] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c57e8fc17c7..d0ca48a0a8dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - `[expect]` Highlight substring differences when matcher fails, part 1 ([#8448](https://github.com/facebook/jest/pull/8448)) - `[expect]` Highlight substring differences when matcher fails, part 2 ([#8528](https://github.com/facebook/jest/pull/8528)) - `[expect]` Improve report when mock-spy matcher fails, part 1 ([#8640](https://github.com/facebook/jest/pull/8640)) +- `[expect]` Improve report when mock-spy matcher fails, part 2 ([#8649](https://github.com/facebook/jest/pull/8649)) - `[jest-snapshot]` Highlight substring differences when matcher fails, part 3 ([#8569](https://github.com/facebook/jest/pull/8569)) - `[jest-cli]` Improve chai support (with detailed output, to match jest exceptions) ([#8454](https://github.com/facebook/jest/pull/8454)) - `[*]` Manage the global timeout with `--testTimeout` command line argument. ([#8456](https://github.com/facebook/jest/pull/8456)) From 9e1d30746d8f6c6341e3268cb21c21c30a4a0ec4 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 8 Jul 2019 11:18:56 -0400 Subject: [PATCH 3/6] Display received number of calls when not equal to returns --- .../__snapshots__/spyMatchers.test.js.snap | 54 +++++++++++++------ .../expect/src/__tests__/spyMatchers.test.js | 4 +- packages/expect/src/spyMatchers.ts | 28 ++++++++-- 3 files changed, 64 insertions(+), 22 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap index 6cc9f96b88ca..0cee3adb9af2 100644 --- a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap @@ -1867,14 +1867,16 @@ exports[`toHaveReturned .not passes when a call throws undefined 1`] = ` "expect(jest.fn()).toHaveReturned() Expected number of returns: >= 1 -Received number of returns: 0" +Received number of returns: 0 +Received number of calls: 1" `; exports[`toHaveReturned .not passes when all calls throw 1`] = ` "expect(jest.fn()).toHaveReturned() Expected number of returns: >= 1 -Received number of returns: 0" +Received number of returns: 0 +Received number of calls: 2" `; exports[`toHaveReturned .not passes when not returned 1`] = ` @@ -1915,7 +1917,8 @@ exports[`toHaveReturned incomplete recursive calls are handled properly 1`] = ` "expect(jest.fn()).toHaveReturned() Expected number of returns: >= 1 -Received number of returns: 0" +Received number of returns: 0 +Received number of calls: 4" `; exports[`toHaveReturned passes when at least one call does not throw 1`] = ` @@ -1925,7 +1928,9 @@ Expected number of returns: 0 Received number of returns: 2 1: 42 -3: 42" +3: 42 + +Received number of calls: 3" `; exports[`toHaveReturned passes when returned 1`] = ` @@ -2021,15 +2026,19 @@ Expected number of returns: not 2" `; exports[`toHaveReturnedTimes calls that throw are not counted 1`] = ` -"expect(jest.fn()).not.toHaveReturnedTimes(expected) +"expect(jest.fn()).toHaveReturnedTimes(expected) -Expected number of returns: not 2" +Expected number of returns: 3 +Received number of returns: 2 +Received number of calls: 3" `; exports[`toHaveReturnedTimes calls that throw undefined are not counted 1`] = ` "expect(jest.fn()).not.toHaveReturnedTimes(expected) -Expected number of returns: not 2" +Expected number of returns: not 2 + +Received number of calls: 3" `; exports[`toHaveReturnedTimes includes the custom mock name in the error message 1`] = ` @@ -2042,7 +2051,9 @@ Received number of returns: 2" exports[`toHaveReturnedTimes incomplete recursive calls are handled properly 1`] = ` "expect(jest.fn()).not.toHaveReturnedTimes(expected) -Expected number of returns: not 2" +Expected number of returns: not 2 + +Received number of calls: 4" `; exports[`toHaveReturnedTimes only accepts a number argument 1`] = ` @@ -2276,14 +2287,16 @@ exports[`toReturn .not passes when a call throws undefined 1`] = ` "expect(jest.fn()).toReturn() Expected number of returns: >= 1 -Received number of returns: 0" +Received number of returns: 0 +Received number of calls: 1" `; exports[`toReturn .not passes when all calls throw 1`] = ` "expect(jest.fn()).toReturn() Expected number of returns: >= 1 -Received number of returns: 0" +Received number of returns: 0 +Received number of calls: 2" `; exports[`toReturn .not passes when not returned 1`] = ` @@ -2324,7 +2337,8 @@ exports[`toReturn incomplete recursive calls are handled properly 1`] = ` "expect(jest.fn()).toReturn() Expected number of returns: >= 1 -Received number of returns: 0" +Received number of returns: 0 +Received number of calls: 4" `; exports[`toReturn passes when at least one call does not throw 1`] = ` @@ -2334,7 +2348,9 @@ Expected number of returns: 0 Received number of returns: 2 1: 42 -3: 42" +3: 42 + +Received number of calls: 3" `; exports[`toReturn passes when returned 1`] = ` @@ -2430,15 +2446,19 @@ Expected number of returns: not 2" `; exports[`toReturnTimes calls that throw are not counted 1`] = ` -"expect(jest.fn()).not.toReturnTimes(expected) +"expect(jest.fn()).toReturnTimes(expected) -Expected number of returns: not 2" +Expected number of returns: 3 +Received number of returns: 2 +Received number of calls: 3" `; exports[`toReturnTimes calls that throw undefined are not counted 1`] = ` "expect(jest.fn()).not.toReturnTimes(expected) -Expected number of returns: not 2" +Expected number of returns: not 2 + +Received number of calls: 3" `; exports[`toReturnTimes includes the custom mock name in the error message 1`] = ` @@ -2451,7 +2471,9 @@ Received number of returns: 2" exports[`toReturnTimes incomplete recursive calls are handled properly 1`] = ` "expect(jest.fn()).not.toReturnTimes(expected) -Expected number of returns: not 2" +Expected number of returns: not 2 + +Received number of calls: 4" `; exports[`toReturnTimes only accepts a number argument 1`] = ` diff --git a/packages/expect/src/__tests__/spyMatchers.test.js b/packages/expect/src/__tests__/spyMatchers.test.js index 54e0b336f236..b2ffb33378dd 100644 --- a/packages/expect/src/__tests__/spyMatchers.test.js +++ b/packages/expect/src/__tests__/spyMatchers.test.js @@ -617,10 +617,10 @@ const jestExpect = require('../'); fn(false); - jestExpect(fn)[returnedTimes](2); + jestExpect(fn).not[returnedTimes](3); expect(() => - jestExpect(fn).not[returnedTimes](2), + jestExpect(fn)[returnedTimes](3), ).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/expect/src/spyMatchers.ts b/packages/expect/src/spyMatchers.ts index cffbd71b2a4e..1b52d88a9439 100644 --- a/packages/expect/src/spyMatchers.ts +++ b/packages/expect/src/spyMatchers.ts @@ -112,12 +112,22 @@ const createToReturnMatcher = (matcherName: string) => return lines; }, []) - .join('\n') + .join('\n') + + (received.mock.calls.length !== count + ? `\n\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : '') : () => matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + `Expected number of returns: >= ${printExpected(1)}\n` + - `Received number of returns: ${printReceived(count)}`; + `Received number of returns: ${printReceived(count)}` + + (received.mock.calls.length !== count + ? `\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : ''); return {message, pass}; }; @@ -186,12 +196,22 @@ const createToReturnTimesMatcher = (matcherName: string) => ? () => matcherHint(matcherName, receivedName, expectedArgument, options) + `\n\n` + - `Expected number of returns: not ${printExpected(expected)}` + `Expected number of returns: not ${printExpected(expected)}` + + (received.mock.calls.length !== count + ? `\n\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : '') : () => matcherHint(matcherName, receivedName, expectedArgument, options) + '\n\n' + `Expected number of returns: ${printExpected(expected)}\n` + - `Received number of returns: ${printReceived(count)}`; + `Received number of returns: ${printReceived(count)}` + + (received.mock.calls.length !== count + ? `\nReceived number of calls: ${printReceived( + received.mock.calls.length, + )}` + : ''); return {message, pass}; }; From 59ebdeda16e1aef4cfab9f9730281b9315789318 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 10 Jul 2019 10:45:10 -0400 Subject: [PATCH 4/6] Call printReceivedArgs to enclose in parentheses which have dim color --- .../__tests__/__snapshots__/spyMatchers.test.js.snap | 8 ++++---- packages/expect/src/__tests__/spyMatchers.test.js | 3 +-- packages/expect/src/spyMatchers.ts | 11 +++++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap index 0cee3adb9af2..b989765b045b 100644 --- a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap @@ -628,7 +628,7 @@ exports[`toBeCalled includes the custom mock name in the error message 1`] = ` Expected number of calls: 0 Received number of calls: 1 -1: []" +1: ()" `; exports[`toBeCalled passes when called 1`] = ` @@ -637,7 +637,7 @@ exports[`toBeCalled passes when called 1`] = ` Expected number of calls: 0 Received number of calls: 1 -1: []" +1: (\\"arg0\\", \\"arg1\\", \\"arg2\\")" `; exports[`toBeCalled works only on spies or jest.fn 1`] = ` @@ -958,7 +958,7 @@ exports[`toHaveBeenCalled includes the custom mock name in the error message 1`] Expected number of calls: 0 Received number of calls: 1 -1: []" +1: ()" `; exports[`toHaveBeenCalled passes when called 1`] = ` @@ -967,7 +967,7 @@ exports[`toHaveBeenCalled passes when called 1`] = ` Expected number of calls: 0 Received number of calls: 1 -1: []" +1: (\\"arg0\\", \\"arg1\\", \\"arg2\\")" `; exports[`toHaveBeenCalled works only on spies or jest.fn 1`] = ` diff --git a/packages/expect/src/__tests__/spyMatchers.test.js b/packages/expect/src/__tests__/spyMatchers.test.js index b2ffb33378dd..710ef46eaf0e 100644 --- a/packages/expect/src/__tests__/spyMatchers.test.js +++ b/packages/expect/src/__tests__/spyMatchers.test.js @@ -3,7 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ const Immutable = require('immutable'); @@ -19,7 +18,7 @@ const jestExpect = require('../'); test(`passes when called`, () => { const fn = jest.fn(); - fn(); + fn('arg0', 'arg1', 'arg2'); jestExpect(fn)[called](); expect(() => jestExpect(fn).not[called]()).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/expect/src/spyMatchers.ts b/packages/expect/src/spyMatchers.ts index 1b52d88a9439..83d069e78bec 100644 --- a/packages/expect/src/spyMatchers.ts +++ b/packages/expect/src/spyMatchers.ts @@ -3,11 +3,11 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * */ import { diff, + DIM_COLOR, ensureExpectedIsNumber, ensureNoExpected, matcherErrorMessage, @@ -27,6 +27,13 @@ const CALL_PRINT_LIMIT = 3; const RETURN_PRINT_LIMIT = 5; const LAST_CALL_PRINT_LIMIT = 1; +const printReceivedArgs = (args: Array): string => + args.length === 0 + ? DIM_COLOR('()') + : DIM_COLOR('(') + + args.map(arg => printReceived(arg)).join(DIM_COLOR(', ')) + + DIM_COLOR(')'); + const createToBeCalledMatcher = (matcherName: string) => function( this: MatcherState, @@ -59,7 +66,7 @@ const createToBeCalledMatcher = (matcherName: string) => calls .reduce((lines: Array, args: any, i: number) => { if (lines.length < PRINT_LIMIT) { - lines.push(`${i + 1}: ${printReceived(args)}`); + lines.push(`${i + 1}: ${printReceivedArgs(args)}`); } return lines; From d0f8d05b0dcc44c0ca67e92c75fef8b0305fc54b Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 10 Jul 2019 13:27:56 -0400 Subject: [PATCH 5/6] Modify printReceivedArgs according to constructive critique --- .../src/__tests__/__snapshots__/spyMatchers.test.js.snap | 8 ++++---- packages/expect/src/spyMatchers.ts | 7 ++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap index b989765b045b..380a7e2f1c3a 100644 --- a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap @@ -628,7 +628,7 @@ exports[`toBeCalled includes the custom mock name in the error message 1`] = ` Expected number of calls: 0 Received number of calls: 1 -1: ()" +1: no args" `; exports[`toBeCalled passes when called 1`] = ` @@ -637,7 +637,7 @@ exports[`toBeCalled passes when called 1`] = ` Expected number of calls: 0 Received number of calls: 1 -1: (\\"arg0\\", \\"arg1\\", \\"arg2\\")" +1: \\"arg0\\", \\"arg1\\", \\"arg2\\"" `; exports[`toBeCalled works only on spies or jest.fn 1`] = ` @@ -958,7 +958,7 @@ exports[`toHaveBeenCalled includes the custom mock name in the error message 1`] Expected number of calls: 0 Received number of calls: 1 -1: ()" +1: no args" `; exports[`toHaveBeenCalled passes when called 1`] = ` @@ -967,7 +967,7 @@ exports[`toHaveBeenCalled passes when called 1`] = ` Expected number of calls: 0 Received number of calls: 1 -1: (\\"arg0\\", \\"arg1\\", \\"arg2\\")" +1: \\"arg0\\", \\"arg1\\", \\"arg2\\"" `; exports[`toHaveBeenCalled works only on spies or jest.fn 1`] = ` diff --git a/packages/expect/src/spyMatchers.ts b/packages/expect/src/spyMatchers.ts index 83d069e78bec..f6e24075acba 100644 --- a/packages/expect/src/spyMatchers.ts +++ b/packages/expect/src/spyMatchers.ts @@ -7,7 +7,6 @@ import { diff, - DIM_COLOR, ensureExpectedIsNumber, ensureNoExpected, matcherErrorMessage, @@ -29,10 +28,8 @@ const LAST_CALL_PRINT_LIMIT = 1; const printReceivedArgs = (args: Array): string => args.length === 0 - ? DIM_COLOR('()') - : DIM_COLOR('(') + - args.map(arg => printReceived(arg)).join(DIM_COLOR(', ')) + - DIM_COLOR(')'); + ? 'no args' + : args.map(arg => printReceived(arg)).join(', '); const createToBeCalledMatcher = (matcherName: string) => function( From b36eeadb6ff856b33da2667606903d81c807919c Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 10 Jul 2019 17:51:49 -0400 Subject: [PATCH 6/6] s/no args/called with no arguments --- .../src/__tests__/__snapshots__/spyMatchers.test.js.snap | 4 ++-- packages/expect/src/spyMatchers.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap index 380a7e2f1c3a..963c808e88c7 100644 --- a/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spyMatchers.test.js.snap @@ -628,7 +628,7 @@ exports[`toBeCalled includes the custom mock name in the error message 1`] = ` Expected number of calls: 0 Received number of calls: 1 -1: no args" +1: called with no arguments" `; exports[`toBeCalled passes when called 1`] = ` @@ -958,7 +958,7 @@ exports[`toHaveBeenCalled includes the custom mock name in the error message 1`] Expected number of calls: 0 Received number of calls: 1 -1: no args" +1: called with no arguments" `; exports[`toHaveBeenCalled passes when called 1`] = ` diff --git a/packages/expect/src/spyMatchers.ts b/packages/expect/src/spyMatchers.ts index f6e24075acba..b21c446bdaa5 100644 --- a/packages/expect/src/spyMatchers.ts +++ b/packages/expect/src/spyMatchers.ts @@ -28,7 +28,7 @@ const LAST_CALL_PRINT_LIMIT = 1; const printReceivedArgs = (args: Array): string => args.length === 0 - ? 'no args' + ? 'called with no arguments' : args.map(arg => printReceived(arg)).join(', '); const createToBeCalledMatcher = (matcherName: string) =>