From 7a3cf01a6a7762550b98e80574fc0468ac66e3d7 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Mon, 26 Mar 2018 21:28:21 -0400 Subject: [PATCH 01/11] Add toReturnValues and toHaveReturnedValues --- docs/ExpectAPI.md | 16 +++++ .../__snapshots__/spy_matchers.test.js.snap | 72 +++++++++++++++++++ .../expect/src/__tests__/spy_matchers.test.js | 67 +++++++++++++++++ packages/expect/src/spy_matchers.js | 34 +++++++++ 4 files changed, 189 insertions(+) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 2fc3e849d086..4dd084374255 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -649,6 +649,22 @@ test('drinkEach drinks each drink', () => { }); ``` +### `.toHaveReturned(value)` + +Also under the alias: `.toReturn(value)` + +If you have a mock function, you can use `.toHaveReturned` to test that the spy +returned a value. For example, let's say you have mock `drink` that returns +`true`. You can write: + +```js +test('drinks returns true', () => { + const drink = jest.fn(() => true); + drink(); + expect(drink).toHaveReturned(true); +}); +``` + ### `.toBeCloseTo(number, numDigits)` Using exact equality with floating point numbers is a bad idea. Rounding means diff --git a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap index fd64c30e1931..1a157848c09a 100644 --- a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap @@ -1059,3 +1059,75 @@ exports[`toHaveBeenNthCalledWith works with trailing undefined arguments 1`] = ` Expected mock function first call to have been called with: Did not expect argument 2 but it was called with undefined." `; + +exports[`toHaveReturned .not passes when called 1`] = ` +"expect(jest.fn()).toHaveReturned(expected) + +Expected mock function to have returned: + \\"Some Other Value\\"" +`; + +exports[`toHaveReturned .not passes when called with no arguments 1`] = ` +"expect(jest.fn()).toHaveReturned(expected) + +Expected mock function to have returned: + undefined" +`; + +exports[`toHaveReturned passes when called 1`] = ` +"expect(jest.fn()).not.toHaveReturned(expected) + +Expected mock function not to have returned: + \\"Return Value\\"" +`; + +exports[`toHaveReturned passes with no arguments 1`] = ` +"expect(jest.fn()).not.toHaveReturned(expected) + +Expected mock function not to have returned: + undefined" +`; + +exports[`toHaveReturned works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toHaveReturned() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`toReturn .not passes when called 1`] = ` +"expect(jest.fn()).toReturn(expected) + +Expected mock function to have returned: + \\"Some Other Value\\"" +`; + +exports[`toReturn .not passes when called with no arguments 1`] = ` +"expect(jest.fn()).toReturn(expected) + +Expected mock function to have returned: + undefined" +`; + +exports[`toReturn passes when called 1`] = ` +"expect(jest.fn()).not.toReturn(expected) + +Expected mock function not to have returned: + \\"Return Value\\"" +`; + +exports[`toReturn passes with no arguments 1`] = ` +"expect(jest.fn()).not.toReturn(expected) + +Expected mock function not to have returned: + undefined" +`; + +exports[`toReturn works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toReturn() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; diff --git a/packages/expect/src/__tests__/spy_matchers.test.js b/packages/expect/src/__tests__/spy_matchers.test.js index 91624ef28515..d4150a0d37a1 100644 --- a/packages/expect/src/__tests__/spy_matchers.test.js +++ b/packages/expect/src/__tests__/spy_matchers.test.js @@ -342,3 +342,70 @@ const jestExpect = require('../'); } }); }); + +['toReturn', 'toHaveReturned'].forEach(called => { + describe(`${called}`, () => { + test(`works only on spies or jest.fn`, () => { + const fn = function fn() {}; + + expect(() => jestExpect(fn)[called]()).toThrowErrorMatchingSnapshot(); + }); + + test(`passes with no arguments`, () => { + const fn = jest.fn(); + fn(); + jestExpect(fn)[called](); + expect(() => jestExpect(fn).not[called]()).toThrowErrorMatchingSnapshot(); + }); + + test(`.not passes when called with no arguments`, () => { + const fn = jest.fn(() => 'Return Value'); + + fn(); + jestExpect(fn).not[called](); + expect(() => jestExpect(fn)[called]()).toThrowErrorMatchingSnapshot(); + }); + + test(`passes when called`, () => { + const fn = jest.fn(() => 'Return Value'); + fn(); + jestExpect(fn)[called]('Return Value'); + expect(() => + jestExpect(fn).not[called]('Return Value'), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`.not passes when called`, () => { + const fn = jest.fn(() => 'Return Value'); + fn(); + jestExpect(fn).not[called]('Some Other Value'); + expect(() => + jestExpect(fn)[called]('Some Other Value'), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`passes with mutliple calls`, () => { + const fn = jest.fn(a => a * 2); + + fn(1); + fn(2); + fn(3); + + jestExpect(fn)[called](2); + jestExpect(fn)[called](4); + jestExpect(fn)[called](6); + }); + + test(`.not passes with multiple calls`, () => { + const fn = jest.fn(a => a * 2); + + fn(1); + fn(2); + fn(3); + + jestExpect(fn).not[called](1); + jestExpect(fn).not[called](3); + jestExpect(fn).not[called](5); + }); + }); +}); diff --git a/packages/expect/src/spy_matchers.js b/packages/expect/src/spy_matchers.js index f1e4e7585dd4..13feb2618e47 100644 --- a/packages/expect/src/spy_matchers.js +++ b/packages/expect/src/spy_matchers.js @@ -118,6 +118,38 @@ const createToBeCalledWithMatcher = matcherName => ( return {message, pass}; }; +const createToReturnValuesMatcher = matcherName => ( + received: any, + expected: any, +) => { + ensureMock(received, matcherName); + + const receivedIsSpy = isSpy(received); + const type = receivedIsSpy ? 'spy' : 'mock function'; + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + + const calls = receivedIsSpy + ? received.returnValues.all().map(x => x.args) + : received.mock.returnValues; + + const [match] = partition(calls, call => equals(expected, call)); + const pass = match.length > 0; + + const message = pass + ? () => + matcherHint('.not' + matcherName, receivedName) + + '\n\n' + + `Expected ${type} not to have returned:\n` + + ` ${printExpected(expected)}` + : () => + matcherHint(matcherName, receivedName) + + '\n\n' + + `Expected ${type} to have returned:\n` + + ` ${printExpected(expected)}`; + + return {message, pass}; +}; + const createLastCalledWithMatcher = matcherName => ( received: any, ...expected: any @@ -206,6 +238,8 @@ const spyMatchers: MatchersObject = { toHaveBeenNthCalledWith: createNthCalledWithMatcher( '.toHaveBeenNthCalledWith', ), + toHaveReturned: createToReturnValuesMatcher('.toHaveReturned'), + toReturn: createToReturnValuesMatcher('.toReturn'), }; const isSpy = spy => spy.calls && typeof spy.calls.count === 'function'; From e25cc3ca94efbf96eb4623b6cdecef0c30bbe6db Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Mon, 26 Mar 2018 22:01:38 -0400 Subject: [PATCH 02/11] Fix typos --- docs/ExpectAPI.md | 2 +- packages/expect/src/spy_matchers.js | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 4dd084374255..9f45608abf1e 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -654,7 +654,7 @@ test('drinkEach drinks each drink', () => { Also under the alias: `.toReturn(value)` If you have a mock function, you can use `.toHaveReturned` to test that the spy -returned a value. For example, let's say you have mock `drink` that returns +returned a value. For example, let's say you have a mock `drink` that returns `true`. You can write: ```js diff --git a/packages/expect/src/spy_matchers.js b/packages/expect/src/spy_matchers.js index 13feb2618e47..5621da35e395 100644 --- a/packages/expect/src/spy_matchers.js +++ b/packages/expect/src/spy_matchers.js @@ -118,10 +118,7 @@ const createToBeCalledWithMatcher = matcherName => ( return {message, pass}; }; -const createToReturnValuesMatcher = matcherName => ( - received: any, - expected: any, -) => { +const createToReturnMatcher = matcherName => (received: any, expected: any) => { ensureMock(received, matcherName); const receivedIsSpy = isSpy(received); @@ -238,8 +235,8 @@ const spyMatchers: MatchersObject = { toHaveBeenNthCalledWith: createNthCalledWithMatcher( '.toHaveBeenNthCalledWith', ), - toHaveReturned: createToReturnValuesMatcher('.toHaveReturned'), - toReturn: createToReturnValuesMatcher('.toReturn'), + toHaveReturned: createToReturnMatcher('.toHaveReturned'), + toReturn: createToReturnMatcher('.toReturn'), }; const isSpy = spy => spy.calls && typeof spy.calls.count === 'function'; From 9c7ec28160990844f5cae1e181d63c5dd280950a Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Tue, 17 Apr 2018 22:23:37 -0400 Subject: [PATCH 03/11] Add spy matchers to pair with called --- .../__snapshots__/spy_matchers.test.js.snap | 794 +++++++++++++++++- .../expect/src/__tests__/spy_matchers.test.js | 309 ++++++- packages/expect/src/spy_matchers.js | 152 +++- 3 files changed, 1190 insertions(+), 65 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap index 1a157848c09a..f38d780069a7 100644 --- a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap @@ -118,6 +118,85 @@ Expected mock function to have been last called with: Did not expect argument 2 but it was called with undefined." `; +exports[`lastReturnedWith works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].lastReturnedWith() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`lastReturnedWith works when not called 1`] = ` +"expect(jest.fn()).lastReturnedWith(expected) + +Expected mock function to have last returned: + \\"foo\\" +But it last returned: + undefined" +`; + +exports[`lastReturnedWith works with Immutable.js objects directly created 1`] = ` +"expect(jest.fn()).not.lastReturnedWith(expected) + +Expected mock function to not have last returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`lastReturnedWith works with Immutable.js objects indirectly created 1`] = ` +"expect(jest.fn()).not.lastReturnedWith(expected) + +Expected mock function to not have last returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`lastReturnedWith works with Map 1`] = ` +"expect(jest.fn()).not.lastReturnedWith(expected) + +Expected mock function to not have last returned: + Map {1 => 2, 2 => 1}" +`; + +exports[`lastReturnedWith works with Map 2`] = ` +"expect(jest.fn()).lastReturnedWith(expected) + +Expected mock function to have last returned: + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"} +But it last returned: + Map {1 => 2, 2 => 1}" +`; + +exports[`lastReturnedWith works with Set 1`] = ` +"expect(jest.fn()).not.lastReturnedWith(expected) + +Expected mock function to not have last returned: + Set {1, 2}" +`; + +exports[`lastReturnedWith works with Set 2`] = ` +"expect(jest.fn()).lastReturnedWith(expected) + +Expected mock function to have last returned: + Set {3, 4} +But it last returned: + Set {1, 2}" +`; + +exports[`lastReturnedWith works with argument that does match 1`] = ` +"expect(jest.fn()).not.lastReturnedWith(expected) + +Expected mock function to not have last returned: + \\"foo\\"" +`; + +exports[`lastReturnedWith works with argument that does not match 1`] = ` +"expect(jest.fn()).lastReturnedWith(expected) + +Expected mock function to have last returned: + \\"bar\\" +But it last returned: + \\"foo\\"" +`; + exports[`nthCalledWith should reject non integer nth value 1`] = `"nth value 0.1 must be a positive integer greater than 0"`; exports[`nthCalledWith should reject nth value smaller than 1 1`] = `"nth value 0 must be a positive integer greater than 0"`; @@ -245,6 +324,102 @@ Expected mock function first call to have been called with: Did not expect argument 2 but it was called with undefined." `; +exports[`nthReturnedWith should reject non integer nth value 1`] = `"nth value 0.1 must be a positive integer greater than 0"`; + +exports[`nthReturnedWith should reject nth value smaller than 1 1`] = `"nth value 0 must be a positive integer greater than 0"`; + +exports[`nthReturnedWith should replace 1st, 2nd, 3rd with first, second, third 1`] = ` +"expect(jest.fn()).nthReturnedWith(expected) + +Expected mock function first call to have returned with: + \\"bar1\\"" +`; + +exports[`nthReturnedWith should replace 1st, 2nd, 3rd with first, second, third 2`] = ` +"expect(jest.fn()).not.nthReturnedWith(expected) + +Expected mock function first call to not have returned with: + \\"foo1\\"" +`; + +exports[`nthReturnedWith works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].nthReturnedWith() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`nthReturnedWith works when not called 1`] = ` +"expect(jest.fn()).nthReturnedWith(expected) + +Expected mock function first call to have returned with: + \\"foo\\"" +`; + +exports[`nthReturnedWith works with Immutable.js objects directly created 1`] = ` +"expect(jest.fn()).not.nthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`nthReturnedWith works with Immutable.js objects indirectly created 1`] = ` +"expect(jest.fn()).not.nthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`nthReturnedWith works with Map 1`] = ` +"expect(jest.fn()).not.nthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Map {1 => 2, 2 => 1}" +`; + +exports[`nthReturnedWith works with Map 2`] = ` +"expect(jest.fn()).nthReturnedWith(expected) + +Expected mock function first call to have returned with: + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" +`; + +exports[`nthReturnedWith works with Set 1`] = ` +"expect(jest.fn()).not.nthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Set {1, 2}" +`; + +exports[`nthReturnedWith works with Set 2`] = ` +"expect(jest.fn()).nthReturnedWith(expected) + +Expected mock function first call to have returned with: + Set {3, 4}" +`; + +exports[`nthReturnedWith works with argument that does match 1`] = ` +"expect(jest.fn()).not.nthReturnedWith(expected) + +Expected mock function first call to not have returned with: + \\"foo\\"" +`; + +exports[`nthReturnedWith works with argument that does not match 1`] = ` +"expect(jest.fn()).nthReturnedWith(expected) + +Expected mock function first call to have returned with: + \\"bar\\"" +`; + +exports[`nthReturnedWith works with three calls 1`] = ` +"expect(jest.fn()).not.nthReturnedWith(expected) + +Expected mock function first call to not have returned with: + \\"foo1\\"" +`; + exports[`toBeCalled .not fails with any argument passed 1`] = ` "expect(received)[.not].toBeCalled() @@ -1060,32 +1235,208 @@ Expected mock function first call to have been called with: Did not expect argument 2 but it was called with undefined." `; -exports[`toHaveReturned .not passes when called 1`] = ` -"expect(jest.fn()).toHaveReturned(expected) +exports[`toHaveLastReturnedWith works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toHaveLastReturnedWith() -Expected mock function to have returned: - \\"Some Other Value\\"" +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" `; -exports[`toHaveReturned .not passes when called with no arguments 1`] = ` -"expect(jest.fn()).toHaveReturned(expected) +exports[`toHaveLastReturnedWith works when not called 1`] = ` +"expect(jest.fn()).toHaveLastReturnedWith(expected) -Expected mock function to have returned: - undefined" +Expected mock function to have last returned: + \\"foo\\" +But it last returned: + undefined" `; -exports[`toHaveReturned passes when called 1`] = ` -"expect(jest.fn()).not.toHaveReturned(expected) +exports[`toHaveLastReturnedWith works with Immutable.js objects directly created 1`] = ` +"expect(jest.fn()).not.toHaveLastReturnedWith(expected) -Expected mock function not to have returned: - \\"Return Value\\"" +Expected mock function to not have last returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; -exports[`toHaveReturned passes with no arguments 1`] = ` -"expect(jest.fn()).not.toHaveReturned(expected) +exports[`toHaveLastReturnedWith works with Immutable.js objects indirectly created 1`] = ` +"expect(jest.fn()).not.toHaveLastReturnedWith(expected) -Expected mock function not to have returned: - undefined" +Expected mock function to not have last returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`toHaveLastReturnedWith works with Map 1`] = ` +"expect(jest.fn()).not.toHaveLastReturnedWith(expected) + +Expected mock function to not have last returned: + Map {1 => 2, 2 => 1}" +`; + +exports[`toHaveLastReturnedWith works with Map 2`] = ` +"expect(jest.fn()).toHaveLastReturnedWith(expected) + +Expected mock function to have last returned: + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"} +But it last returned: + Map {1 => 2, 2 => 1}" +`; + +exports[`toHaveLastReturnedWith works with Set 1`] = ` +"expect(jest.fn()).not.toHaveLastReturnedWith(expected) + +Expected mock function to not have last returned: + Set {1, 2}" +`; + +exports[`toHaveLastReturnedWith works with Set 2`] = ` +"expect(jest.fn()).toHaveLastReturnedWith(expected) + +Expected mock function to have last returned: + Set {3, 4} +But it last returned: + Set {1, 2}" +`; + +exports[`toHaveLastReturnedWith works with argument that does match 1`] = ` +"expect(jest.fn()).not.toHaveLastReturnedWith(expected) + +Expected mock function to not have last returned: + \\"foo\\"" +`; + +exports[`toHaveLastReturnedWith works with argument that does not match 1`] = ` +"expect(jest.fn()).toHaveLastReturnedWith(expected) + +Expected mock function to have last returned: + \\"bar\\" +But it last returned: + \\"foo\\"" +`; + +exports[`toHaveNthReturnedWith should reject non integer nth value 1`] = `"nth value 0.1 must be a positive integer greater than 0"`; + +exports[`toHaveNthReturnedWith should reject nth value smaller than 1 1`] = `"nth value 0 must be a positive integer greater than 0"`; + +exports[`toHaveNthReturnedWith should replace 1st, 2nd, 3rd with first, second, third 1`] = ` +"expect(jest.fn()).toHaveNthReturnedWith(expected) + +Expected mock function first call to have returned with: + \\"bar1\\"" +`; + +exports[`toHaveNthReturnedWith should replace 1st, 2nd, 3rd with first, second, third 2`] = ` +"expect(jest.fn()).not.toHaveNthReturnedWith(expected) + +Expected mock function first call to not have returned with: + \\"foo1\\"" +`; + +exports[`toHaveNthReturnedWith works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toHaveNthReturnedWith() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`toHaveNthReturnedWith works when not called 1`] = ` +"expect(jest.fn()).toHaveNthReturnedWith(expected) + +Expected mock function first call to have returned with: + \\"foo\\"" +`; + +exports[`toHaveNthReturnedWith works with Immutable.js objects directly created 1`] = ` +"expect(jest.fn()).not.toHaveNthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`toHaveNthReturnedWith works with Immutable.js objects indirectly created 1`] = ` +"expect(jest.fn()).not.toHaveNthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`toHaveNthReturnedWith works with Map 1`] = ` +"expect(jest.fn()).not.toHaveNthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Map {1 => 2, 2 => 1}" +`; + +exports[`toHaveNthReturnedWith works with Map 2`] = ` +"expect(jest.fn()).toHaveNthReturnedWith(expected) + +Expected mock function first call to have returned with: + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" +`; + +exports[`toHaveNthReturnedWith works with Set 1`] = ` +"expect(jest.fn()).not.toHaveNthReturnedWith(expected) + +Expected mock function first call to not have returned with: + Set {1, 2}" +`; + +exports[`toHaveNthReturnedWith works with Set 2`] = ` +"expect(jest.fn()).toHaveNthReturnedWith(expected) + +Expected mock function first call to have returned with: + Set {3, 4}" +`; + +exports[`toHaveNthReturnedWith works with argument that does match 1`] = ` +"expect(jest.fn()).not.toHaveNthReturnedWith(expected) + +Expected mock function first call to not have returned with: + \\"foo\\"" +`; + +exports[`toHaveNthReturnedWith works with argument that does not match 1`] = ` +"expect(jest.fn()).toHaveNthReturnedWith(expected) + +Expected mock function first call to have returned with: + \\"bar\\"" +`; + +exports[`toHaveNthReturnedWith works with three calls 1`] = ` +"expect(jest.fn()).not.toHaveNthReturnedWith(expected) + +Expected mock function first call to not have returned with: + \\"foo1\\"" +`; + +exports[`toHaveReturned .not fails with any argument passed 1`] = ` +"expect(received)[.not].toHaveReturned() + +Matcher does not accept any arguments. +Got: + number: 555" +`; + +exports[`toHaveReturned .not passes when not returned 1`] = ` +"expect(jest.fn()).toHaveReturned() + +Expected mock function to have returned." +`; + +exports[`toHaveReturned fails with any argument passed 1`] = ` +"expect(received)[.not].toHaveReturned() + +Matcher does not accept any arguments. +Got: + number: 555" +`; + +exports[`toHaveReturned passes when returned 1`] = ` +"expect(jest.fn()).not.toHaveReturned() + +Expected mock function not to have returned, but it returned: + 42" `; exports[`toHaveReturned works only on spies or jest.fn 1`] = ` @@ -1096,32 +1447,226 @@ Received: function: [Function fn]" `; -exports[`toReturn .not passes when called 1`] = ` -"expect(jest.fn()).toReturn(expected) +exports[`toHaveReturnedTimes .not only accepts a number argument 1`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + object: {}" +`; + +exports[`toHaveReturnedTimes .not only accepts a number argument 2`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + array: []" +`; + +exports[`toHaveReturnedTimes .not only accepts a number argument 3`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + boolean: true" +`; + +exports[`toHaveReturnedTimes .not only accepts a number argument 4`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + string: \\"a\\"" +`; + +exports[`toHaveReturnedTimes .not only accepts a number argument 5`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + map: Map {}" +`; + +exports[`toHaveReturnedTimes .not only accepts a number argument 6`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + function: [Function anonymous]" +`; + +exports[`toHaveReturnedTimes .not passes if function called less than expected times 1`] = ` +"expect(jest.fn()).toHaveReturnedTimes(2) + +Expected mock function to have returned two times, but it returned one time." +`; + +exports[`toHaveReturnedTimes .not passes if function returned more than expected times 1`] = ` +"expect(jest.fn()).toHaveReturnedTimes(2) + +Expected mock function to have returned two times, but it returned three times." +`; + +exports[`toHaveReturnedTimes only accepts a number argument 1`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + object: {}" +`; + +exports[`toHaveReturnedTimes only accepts a number argument 2`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + array: []" +`; + +exports[`toHaveReturnedTimes only accepts a number argument 3`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + boolean: true" +`; + +exports[`toHaveReturnedTimes only accepts a number argument 4`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + string: \\"a\\"" +`; + +exports[`toHaveReturnedTimes only accepts a number argument 5`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + map: Map {}" +`; + +exports[`toHaveReturnedTimes only accepts a number argument 6`] = ` +"expect(received)[.not].toHaveReturnedTimes(expected) + +Expected value must be a number. +Got: + function: [Function anonymous]" +`; + +exports[`toHaveReturnedTimes passes if function returned equal to expected times 1`] = ` +"expect(jest.fn()).not.toHaveReturnedTimes(2) + +Expected mock function not to have returned two times, but it returned exactly two times." +`; + +exports[`toHaveReturnedTimes works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toHaveReturnedTimes() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`toHaveReturnedWith works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toHaveReturnedWith() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`toHaveReturnedWith works when not called 1`] = ` +"expect(jest.fn()).toHaveReturnedWith(expected) Expected mock function to have returned: - \\"Some Other Value\\"" + \\"foo\\"" +`; + +exports[`toHaveReturnedWith works with Immutable.js objects directly created 1`] = ` +"expect(jest.fn()).not.toHaveReturnedWith(expected) + +Expected mock function not to have returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`toHaveReturnedWith works with Immutable.js objects indirectly created 1`] = ` +"expect(jest.fn()).not.toHaveReturnedWith(expected) + +Expected mock function not to have returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; -exports[`toReturn .not passes when called with no arguments 1`] = ` -"expect(jest.fn()).toReturn(expected) +exports[`toHaveReturnedWith works with Map 1`] = ` +"expect(jest.fn()).not.toHaveReturnedWith(expected) + +Expected mock function not to have returned: + Map {1 => 2, 2 => 1}" +`; + +exports[`toHaveReturnedWith works with Map 2`] = ` +"expect(jest.fn()).toHaveReturnedWith(expected) Expected mock function to have returned: - undefined" + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" `; -exports[`toReturn passes when called 1`] = ` -"expect(jest.fn()).not.toReturn(expected) +exports[`toHaveReturnedWith works with Set 1`] = ` +"expect(jest.fn()).not.toHaveReturnedWith(expected) Expected mock function not to have returned: - \\"Return Value\\"" + Set {1, 2}" +`; + +exports[`toHaveReturnedWith works with Set 2`] = ` +"expect(jest.fn()).toHaveReturnedWith(expected) + +Expected mock function to have returned: + Set {3, 4}" `; -exports[`toReturn passes with no arguments 1`] = ` -"expect(jest.fn()).not.toReturn(expected) +exports[`toHaveReturnedWith works with argument that does match 1`] = ` +"expect(jest.fn()).not.toHaveReturnedWith(expected) Expected mock function not to have returned: - undefined" + \\"foo\\"" +`; + +exports[`toHaveReturnedWith works with argument that does not match 1`] = ` +"expect(jest.fn()).toHaveReturnedWith(expected) + +Expected mock function to have returned: + \\"bar\\"" +`; + +exports[`toReturn .not fails with any argument passed 1`] = ` +"expect(received)[.not].toReturn() + +Matcher does not accept any arguments. +Got: + number: 555" +`; + +exports[`toReturn .not passes when not returned 1`] = ` +"expect(jest.fn()).toReturn() + +Expected mock function to have returned." +`; + +exports[`toReturn fails with any argument passed 1`] = ` +"expect(received)[.not].toReturn() + +Matcher does not accept any arguments. +Got: + number: 555" +`; + +exports[`toReturn passes when returned 1`] = ` +"expect(jest.fn()).not.toReturn() + +Expected mock function not to have returned, but it returned: + 42" `; exports[`toReturn works only on spies or jest.fn 1`] = ` @@ -1131,3 +1676,196 @@ exports[`toReturn works only on spies or jest.fn 1`] = ` Received: function: [Function fn]" `; + +exports[`toReturnTimes .not only accepts a number argument 1`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + object: {}" +`; + +exports[`toReturnTimes .not only accepts a number argument 2`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + array: []" +`; + +exports[`toReturnTimes .not only accepts a number argument 3`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + boolean: true" +`; + +exports[`toReturnTimes .not only accepts a number argument 4`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + string: \\"a\\"" +`; + +exports[`toReturnTimes .not only accepts a number argument 5`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + map: Map {}" +`; + +exports[`toReturnTimes .not only accepts a number argument 6`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + function: [Function anonymous]" +`; + +exports[`toReturnTimes .not passes if function called less than expected times 1`] = ` +"expect(jest.fn()).toReturnTimes(2) + +Expected mock function to have returned two times, but it returned one time." +`; + +exports[`toReturnTimes .not passes if function returned more than expected times 1`] = ` +"expect(jest.fn()).toReturnTimes(2) + +Expected mock function to have returned two times, but it returned three times." +`; + +exports[`toReturnTimes only accepts a number argument 1`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + object: {}" +`; + +exports[`toReturnTimes only accepts a number argument 2`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + array: []" +`; + +exports[`toReturnTimes only accepts a number argument 3`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + boolean: true" +`; + +exports[`toReturnTimes only accepts a number argument 4`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + string: \\"a\\"" +`; + +exports[`toReturnTimes only accepts a number argument 5`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + map: Map {}" +`; + +exports[`toReturnTimes only accepts a number argument 6`] = ` +"expect(received)[.not].toReturnTimes(expected) + +Expected value must be a number. +Got: + function: [Function anonymous]" +`; + +exports[`toReturnTimes passes if function returned equal to expected times 1`] = ` +"expect(jest.fn()).not.toReturnTimes(2) + +Expected mock function not to have returned two times, but it returned exactly two times." +`; + +exports[`toReturnTimes works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toReturnTimes() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`toReturnWith works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toReturnWith() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`toReturnWith works when not called 1`] = ` +"expect(jest.fn()).toReturnWith(expected) + +Expected mock function to have returned: + \\"foo\\"" +`; + +exports[`toReturnWith works with Immutable.js objects directly created 1`] = ` +"expect(jest.fn()).not.toReturnWith(expected) + +Expected mock function not to have returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`toReturnWith works with Immutable.js objects indirectly created 1`] = ` +"expect(jest.fn()).not.toReturnWith(expected) + +Expected mock function not to have returned: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" +`; + +exports[`toReturnWith works with Map 1`] = ` +"expect(jest.fn()).not.toReturnWith(expected) + +Expected mock function not to have returned: + Map {1 => 2, 2 => 1}" +`; + +exports[`toReturnWith works with Map 2`] = ` +"expect(jest.fn()).toReturnWith(expected) + +Expected mock function to have returned: + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" +`; + +exports[`toReturnWith works with Set 1`] = ` +"expect(jest.fn()).not.toReturnWith(expected) + +Expected mock function not to have returned: + Set {1, 2}" +`; + +exports[`toReturnWith works with Set 2`] = ` +"expect(jest.fn()).toReturnWith(expected) + +Expected mock function to have returned: + Set {3, 4}" +`; + +exports[`toReturnWith works with argument that does match 1`] = ` +"expect(jest.fn()).not.toReturnWith(expected) + +Expected mock function not to have returned: + \\"foo\\"" +`; + +exports[`toReturnWith works with argument that does not match 1`] = ` +"expect(jest.fn()).toReturnWith(expected) + +Expected mock function to have returned: + \\"bar\\"" +`; diff --git a/packages/expect/src/__tests__/spy_matchers.test.js b/packages/expect/src/__tests__/spy_matchers.test.js index d4150a0d37a1..be9f927d7be3 100644 --- a/packages/expect/src/__tests__/spy_matchers.test.js +++ b/packages/expect/src/__tests__/spy_matchers.test.js @@ -343,69 +343,310 @@ const jestExpect = require('../'); }); }); -['toReturn', 'toHaveReturned'].forEach(called => { - describe(`${called}`, () => { +['toReturn', 'toHaveReturned'].forEach(returned => { + describe(`${returned}`, () => { test(`works only on spies or jest.fn`, () => { const fn = function fn() {}; - expect(() => jestExpect(fn)[called]()).toThrowErrorMatchingSnapshot(); + expect(() => jestExpect(fn)[returned]()).toThrowErrorMatchingSnapshot(); + }); + + test(`passes when returned`, () => { + const fn = jest.fn(() => 42); + fn(); + jestExpect(fn)[returned](); + expect(() => + jestExpect(fn).not[returned](), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`.not passes when not returned`, () => { + const fn = jest.fn(); + + jestExpect(fn).not[returned](); + expect(() => jestExpect(fn)[returned]()).toThrowErrorMatchingSnapshot(); }); - test(`passes with no arguments`, () => { + test(`fails with any argument passed`, () => { const fn = jest.fn(); + fn(); - jestExpect(fn)[called](); - expect(() => jestExpect(fn).not[called]()).toThrowErrorMatchingSnapshot(); + expect(() => + jestExpect(fn)[returned](555), + ).toThrowErrorMatchingSnapshot(); }); - test(`.not passes when called with no arguments`, () => { - const fn = jest.fn(() => 'Return Value'); + test(`.not fails with any argument passed`, () => { + const fn = jest.fn(); + expect(() => + jestExpect(fn).not[returned](555), + ).toThrowErrorMatchingSnapshot(); + }); + }); +}); + +['toReturnTimes', 'toHaveReturnedTimes'].forEach(returnedTimes => { + describe(`${returnedTimes}`, () => { + test('works only on spies or jest.fn', () => { + const fn = function fn() {}; + + expect(() => + jestExpect(fn)[returnedTimes](2), + ).toThrowErrorMatchingSnapshot(); + }); + + test('only accepts a number argument', () => { + const fn = jest.fn(() => 42); fn(); - jestExpect(fn).not[called](); - expect(() => jestExpect(fn)[called]()).toThrowErrorMatchingSnapshot(); + jestExpect(fn)[returnedTimes](1); + + [{}, [], true, 'a', new Map(), () => {}].forEach(value => { + expect(() => + jestExpect(fn)[returnedTimes](value), + ).toThrowErrorMatchingSnapshot(); + }); }); - test(`passes when called`, () => { - const fn = jest.fn(() => 'Return Value'); + test('.not only accepts a number argument', () => { + const fn = jest.fn(() => 42); + jestExpect(fn).not[returnedTimes](2); + + [{}, [], true, 'a', new Map(), () => {}].forEach(value => { + expect(() => + jestExpect(fn).not[returnedTimes](value), + ).toThrowErrorMatchingSnapshot(); + }); + }); + + test('passes if function returned equal to expected times', () => { + const fn = jest.fn(() => 42); + fn(); fn(); - jestExpect(fn)[called]('Return Value'); + + jestExpect(fn)[returnedTimes](2); + expect(() => - jestExpect(fn).not[called]('Return Value'), + jestExpect(fn).not[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); - test(`.not passes when called`, () => { - const fn = jest.fn(() => 'Return Value'); + test('.not passes if function returned more than expected times', () => { + const fn = jest.fn(() => 42); + fn(); + fn(); + fn(); + + jestExpect(fn)[returnedTimes](3); + jestExpect(fn).not[returnedTimes](2); + + expect(() => + jestExpect(fn)[returnedTimes](2), + ).toThrowErrorMatchingSnapshot(); + }); + + test('.not passes if function called less than expected times', () => { + const fn = jest.fn(() => 42); fn(); - jestExpect(fn).not[called]('Some Other Value'); + + jestExpect(fn)[returnedTimes](1); + jestExpect(fn).not[returnedTimes](2); + expect(() => - jestExpect(fn)[called]('Some Other Value'), + jestExpect(fn)[returnedTimes](2), ).toThrowErrorMatchingSnapshot(); }); + }); +}); - test(`passes with mutliple calls`, () => { - const fn = jest.fn(a => a * 2); +[ + 'lastReturnedWith', + 'toHaveLastReturnedWith', + 'nthReturnedWith', + 'toHaveNthReturnedWith', + 'toReturnWith', + 'toHaveReturnedWith', +].forEach(returnedWith => { + const caller = function(callee, ...args) { + if ( + returnedWith === 'nthReturnedWith' || + returnedWith === 'toHaveNthReturnedWith' + ) { + callee(1, ...args); + } else { + callee(...args); + } + }; - fn(1); - fn(2); - fn(3); + describe(`${returnedWith}`, () => { + test(`works only on spies or jest.fn`, () => { + const fn = function fn() {}; - jestExpect(fn)[called](2); - jestExpect(fn)[called](4); - jestExpect(fn)[called](6); + expect(() => + jestExpect(fn)[returnedWith](), + ).toThrowErrorMatchingSnapshot(); }); - test(`.not passes with multiple calls`, () => { - const fn = jest.fn(a => a * 2); + test(`works when not called`, () => { + const fn = jest.fn(); + caller(jestExpect(fn).not[returnedWith], 'foo'); - fn(1); - fn(2); - fn(3); + expect(() => + caller(jestExpect(fn)[returnedWith], 'foo'), + ).toThrowErrorMatchingSnapshot(); + }); - jestExpect(fn).not[called](1); - jestExpect(fn).not[called](3); - jestExpect(fn).not[called](5); + test(`works with no arguments`, () => { + const fn = jest.fn(); + fn(); + caller(jestExpect(fn)[returnedWith]); }); + + test('works with argument that does not match', () => { + const fn = jest.fn(() => 'foo'); + fn(); + + caller(jestExpect(fn).not[returnedWith], 'bar'); + + expect(() => + caller(jestExpect(fn)[returnedWith], 'bar'), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`works with argument that does match`, () => { + const fn = jest.fn(() => 'foo'); + fn(); + + caller(jestExpect(fn)[returnedWith], 'foo'); + + expect(() => + caller(jestExpect(fn).not[returnedWith], 'foo'), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`works with Map`, () => { + const m1 = new Map([[1, 2], [2, 1]]); + const m2 = new Map([[1, 2], [2, 1]]); + const m3 = new Map([['a', 'b'], ['b', 'a']]); + + const fn = jest.fn(() => m1); + fn(); + + caller(jestExpect(fn)[returnedWith], m2); + caller(jestExpect(fn).not[returnedWith], m3); + + expect(() => + caller(jestExpect(fn).not[returnedWith], m2), + ).toThrowErrorMatchingSnapshot(); + expect(() => + caller(jestExpect(fn)[returnedWith], m3), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`works with Set`, () => { + const s1 = new Set([1, 2]); + const s2 = new Set([1, 2]); + const s3 = new Set([3, 4]); + + const fn = jest.fn(() => s1); + fn(); + + caller(jestExpect(fn)[returnedWith], s2); + caller(jestExpect(fn).not[returnedWith], s3); + + expect(() => + caller(jestExpect(fn).not[returnedWith], s2), + ).toThrowErrorMatchingSnapshot(); + expect(() => + caller(jestExpect(fn)[returnedWith], s3), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`works with Immutable.js objects directly created`, () => { + const directlyCreated = new Immutable.Map([['a', {b: 'c'}]]); + const fn = jest.fn(() => directlyCreated); + fn(); + + caller(jestExpect(fn)[returnedWith], directlyCreated); + + expect(() => + caller(jestExpect(fn).not[returnedWith], directlyCreated), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`works with Immutable.js objects indirectly created`, () => { + const indirectlyCreated = new Immutable.Map().set('a', {b: 'c'}); + const fn = jest.fn(() => indirectlyCreated); + fn(); + + caller(jestExpect(fn)[returnedWith], indirectlyCreated); + + expect(() => + caller(jestExpect(fn).not[returnedWith], indirectlyCreated), + ).toThrowErrorMatchingSnapshot(); + }); + + const nthCalled = ['toHaveNthReturnedWith', 'nthReturnedWith']; + if (nthCalled.indexOf(returnedWith) >= 0) { + test(`works with three calls`, () => { + const fn = jest.fn(); + fn.mockReturnValueOnce('foo1'); + fn.mockReturnValueOnce('foo2'); + fn.mockReturnValueOnce('foo3'); + fn(); + fn(); + fn(); + + jestExpect(fn)[returnedWith](1, 'foo1'); + jestExpect(fn)[returnedWith](2, 'foo2'); + jestExpect(fn)[returnedWith](3, 'foo3'); + + expect(() => { + jestExpect(fn).not[returnedWith](1, 'foo1'); + jestExpect(fn).not[returnedWith](2, 'foo2'); + jestExpect(fn).not[returnedWith](3, 'foo3'); + }).toThrowErrorMatchingSnapshot(); + }); + + test('should replace 1st, 2nd, 3rd with first, second, third', async () => { + const fn = jest.fn(); + fn.mockReturnValueOnce('foo1'); + fn.mockReturnValueOnce('foo2'); + fn.mockReturnValueOnce('foo3'); + fn(); + fn(); + fn(); + + expect(() => { + jestExpect(fn)[returnedWith](1, 'bar1'); + jestExpect(fn)[returnedWith](2, 'bar2'); + jestExpect(fn)[returnedWith](3, 'bar3'); + }).toThrowErrorMatchingSnapshot(); + + expect(() => { + jestExpect(fn).not[returnedWith](1, 'foo1'); + jestExpect(fn).not[returnedWith](2, 'foo2'); + jestExpect(fn).not[returnedWith](3, 'foo3'); + }).toThrowErrorMatchingSnapshot(); + }); + + test('should reject nth value smaller than 1', async () => { + const fn = jest.fn(() => 'foo'); + fn(); + + expect(() => { + jestExpect(fn)[returnedWith](0, 'foo'); + }).toThrowErrorMatchingSnapshot(); + }); + + test('should reject non integer nth value', async () => { + const fn = jest.fn(() => 'foo'); + fn('foo'); + + expect(() => { + jestExpect(fn)[returnedWith](0.1, 'foo'); + }).toThrowErrorMatchingSnapshot(); + }); + } }); }); diff --git a/packages/expect/src/spy_matchers.js b/packages/expect/src/spy_matchers.js index 5621da35e395..6266dac5e7b5 100644 --- a/packages/expect/src/spy_matchers.js +++ b/packages/expect/src/spy_matchers.js @@ -54,6 +54,32 @@ const createToBeCalledMatcher = matcherName => (received, expected) => { return {message, pass}; }; +const createToReturnMatcher = matcherName => (received, expected) => { + ensureNoExpected(expected, matcherName); + ensureMock(received, matcherName); + + const receivedIsSpy = isSpy(received); + const type = receivedIsSpy ? 'spy' : 'mock function'; + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + const count = receivedIsSpy + ? received.returnValues.count() + : received.mock.returnValues.length; + + const pass = count > 0; + const message = pass + ? () => + matcherHint('.not' + matcherName, receivedName, '') + + '\n\n' + + `Expected ${type} not to have returned, but it returned:\n` + + ` ${RECEIVED_COLOR(received.mock.returnValues.join(', '))}` + : () => + matcherHint(matcherName, receivedName, '') + + '\n\n' + + `Expected ${type} to have returned.`; + + return {message, pass}; +}; + const createToBeCalledTimesMatcher = (matcherName: string) => ( received: any, expected: number, @@ -85,6 +111,38 @@ const createToBeCalledTimesMatcher = (matcherName: string) => ( return {message, pass}; }; +const createToReturnTimesMatcher = (matcherName: string) => ( + received: any, + expected: number, +) => { + ensureExpectedIsNumber(expected, matcherName); + ensureMock(received, matcherName); + + const receivedIsSpy = isSpy(received); + const type = receivedIsSpy ? 'spy' : 'mock function'; + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + const count = receivedIsSpy + ? received.returnValues.length() + : received.mock.returnValues.length; + + const pass = count === expected; + const message = pass + ? () => + matcherHint('.not' + matcherName, receivedName, String(expected)) + + `\n\n` + + `Expected ${type} not to have returned ` + + `${EXPECTED_COLOR(pluralize('time', expected))}, but it` + + ` returned exactly ${RECEIVED_COLOR(pluralize('time', count))}.` + : () => + matcherHint(matcherName, receivedName, String(expected)) + + '\n\n' + + `Expected ${type} to have returned ` + + `${EXPECTED_COLOR(pluralize('time', expected))},` + + ` but it returned ${RECEIVED_COLOR(pluralize('time', count))}.`; + + return {message, pass}; +}; + const createToBeCalledWithMatcher = matcherName => ( received: any, ...expected: any @@ -118,18 +176,23 @@ const createToBeCalledWithMatcher = matcherName => ( return {message, pass}; }; -const createToReturnMatcher = matcherName => (received: any, expected: any) => { +const createToReturnWithMatcher = matcherName => ( + received: any, + expected: any, +) => { ensureMock(received, matcherName); const receivedIsSpy = isSpy(received); const type = receivedIsSpy ? 'spy' : 'mock function'; const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - const calls = receivedIsSpy + const returnValues = receivedIsSpy ? received.returnValues.all().map(x => x.args) : received.mock.returnValues; - const [match] = partition(calls, call => equals(expected, call)); + const [match] = partition(returnValues, value => + equals(expected, value, [iterableEquality]), + ); const pass = match.length > 0; const message = pass @@ -176,6 +239,39 @@ const createLastCalledWithMatcher = matcherName => ( return {message, pass}; }; +const createLastReturnedMatcher = matcherName => ( + received: any, + expected: any, +) => { + ensureMock(received, matcherName); + + const receivedIsSpy = isSpy(received); + const type = receivedIsSpy ? 'spy' : 'mock function'; + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + const returnValues = receivedIsSpy + ? received.returnValues.all().map(x => x.args) + : received.mock.returnValues; + + const lastReturnValue = returnValues[returnValues.length - 1]; + const pass = equals(lastReturnValue, expected, [iterableEquality]); + + const message = pass + ? () => + matcherHint('.not' + matcherName, receivedName) + + '\n\n' + + `Expected ${type} to not have last returned:\n` + + ` ${printExpected(expected)}` + : () => + matcherHint(matcherName, receivedName) + + '\n\n' + + `Expected ${type} to have last returned:\n` + + ` ${printExpected(expected)}\n` + + `But it last returned:\n` + + ` ${printReceived(lastReturnValue)}`; + + return {message, pass}; +}; + const createNthCalledWithMatcher = (matcherName: string) => ( received: any, nth: number, @@ -220,9 +316,53 @@ const createNthCalledWithMatcher = (matcherName: string) => ( return {message, pass}; }; +const createNthReturnedWithMatcher = (matcherName: string) => ( + received: any, + nth: number, + expected: any, +) => { + ensureMock(received, matcherName); + + const receivedIsSpy = isSpy(received); + const type = receivedIsSpy ? 'spy' : 'mock function'; + + if (typeof nth !== 'number' || parseInt(nth, 10) !== nth || nth < 1) { + const message = () => + `nth value ${printReceived( + nth, + )} must be a positive integer greater than ${printExpected(0)}`; + const pass = false; + return {message, pass}; + } + + const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); + const returnValues = receivedIsSpy + ? received.returnValues.all().map(x => x.args) + : received.mock.returnValues; + const pass = equals(returnValues[nth - 1], expected, [iterableEquality]); + + const message = pass + ? () => + matcherHint('.not' + matcherName, receivedName) + + '\n\n' + + `Expected ${type} ${nthToString( + nth, + )} call to not have returned with:\n` + + ` ${printExpected(expected)}` + : () => + matcherHint(matcherName, receivedName) + + '\n\n' + + `Expected ${type} ${nthToString(nth)} call to have returned with:\n` + + ` ${printExpected(expected)}`; + + return {message, pass}; +}; + const spyMatchers: MatchersObject = { lastCalledWith: createLastCalledWithMatcher('.lastCalledWith'), + lastReturnedWith: createLastReturnedMatcher('.lastReturnedWith'), nthCalledWith: createNthCalledWithMatcher('.nthCalledWith'), + nthReturnedWith: createNthReturnedWithMatcher('.nthReturnedWith'), toBeCalled: createToBeCalledMatcher('.toBeCalled'), toBeCalledTimes: createToBeCalledTimesMatcher('.toBeCalledTimes'), toBeCalledWith: createToBeCalledWithMatcher('.toBeCalledWith'), @@ -235,8 +375,14 @@ const spyMatchers: MatchersObject = { toHaveBeenNthCalledWith: createNthCalledWithMatcher( '.toHaveBeenNthCalledWith', ), + toHaveLastReturnedWith: createLastReturnedMatcher('.toHaveLastReturnedWith'), + toHaveNthReturnedWith: createNthReturnedWithMatcher('.toHaveNthReturnedWith'), toHaveReturned: createToReturnMatcher('.toHaveReturned'), + toHaveReturnedTimes: createToReturnTimesMatcher('.toHaveReturnedTimes'), + toHaveReturnedWith: createToReturnWithMatcher('.toHaveReturnedWith'), toReturn: createToReturnMatcher('.toReturn'), + toReturnTimes: createToReturnTimesMatcher('.toReturnTimes'), + toReturnWith: createToReturnWithMatcher('.toReturnWith'), }; const isSpy = spy => spy.calls && typeof spy.calls.count === 'function'; From 19aada6ad8a0e5f0438b287f49e40943a1eb81cf Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Sun, 22 Apr 2018 13:47:28 -0500 Subject: [PATCH 04/11] Clean up matchers --- packages/expect/src/spy_matchers.js | 93 ++++++++++------------------- 1 file changed, 33 insertions(+), 60 deletions(-) diff --git a/packages/expect/src/spy_matchers.js b/packages/expect/src/spy_matchers.js index 6266dac5e7b5..708f879da06f 100644 --- a/packages/expect/src/spy_matchers.js +++ b/packages/expect/src/spy_matchers.js @@ -58,24 +58,19 @@ const createToReturnMatcher = matcherName => (received, expected) => { ensureNoExpected(expected, matcherName); ensureMock(received, matcherName); - const receivedIsSpy = isSpy(received); - const type = receivedIsSpy ? 'spy' : 'mock function'; - const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - const count = receivedIsSpy - ? received.returnValues.count() - : received.mock.returnValues.length; - + const count = received.mock.returnValues.length; const pass = count > 0; + const message = pass ? () => - matcherHint('.not' + matcherName, receivedName, '') + + matcherHint('.not' + matcherName, received.getMockName(), '') + '\n\n' + - `Expected ${type} not to have returned, but it returned:\n` + + `Expected mock function not to have returned, but it returned:\n` + ` ${RECEIVED_COLOR(received.mock.returnValues.join(', '))}` : () => - matcherHint(matcherName, receivedName, '') + + matcherHint(matcherName, received.getMockName(), '') + '\n\n' + - `Expected ${type} to have returned.`; + `Expected mock function to have returned.`; return {message, pass}; }; @@ -118,25 +113,24 @@ const createToReturnTimesMatcher = (matcherName: string) => ( ensureExpectedIsNumber(expected, matcherName); ensureMock(received, matcherName); - const receivedIsSpy = isSpy(received); - const type = receivedIsSpy ? 'spy' : 'mock function'; - const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - const count = receivedIsSpy - ? received.returnValues.length() - : received.mock.returnValues.length; - + const count = received.mock.returnValues.length; const pass = count === expected; + const message = pass ? () => - matcherHint('.not' + matcherName, receivedName, String(expected)) + + matcherHint( + '.not' + matcherName, + received.getMockName(), + String(expected), + ) + `\n\n` + - `Expected ${type} not to have returned ` + + `Expected mock function not to have returned ` + `${EXPECTED_COLOR(pluralize('time', expected))}, but it` + ` returned exactly ${RECEIVED_COLOR(pluralize('time', count))}.` : () => - matcherHint(matcherName, receivedName, String(expected)) + + matcherHint(matcherName, received.getMockName(), String(expected)) + '\n\n' + - `Expected ${type} to have returned ` + + `Expected mock function to have returned ` + `${EXPECTED_COLOR(pluralize('time', expected))},` + ` but it returned ${RECEIVED_COLOR(pluralize('time', count))}.`; @@ -182,14 +176,7 @@ const createToReturnWithMatcher = matcherName => ( ) => { ensureMock(received, matcherName); - const receivedIsSpy = isSpy(received); - const type = receivedIsSpy ? 'spy' : 'mock function'; - const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - - const returnValues = receivedIsSpy - ? received.returnValues.all().map(x => x.args) - : received.mock.returnValues; - + const returnValues = received.mock.returnValues; const [match] = partition(returnValues, value => equals(expected, value, [iterableEquality]), ); @@ -197,14 +184,14 @@ const createToReturnWithMatcher = matcherName => ( const message = pass ? () => - matcherHint('.not' + matcherName, receivedName) + + matcherHint('.not' + matcherName, received.getMockName()) + '\n\n' + - `Expected ${type} not to have returned:\n` + + `Expected mock function not to have returned:\n` + ` ${printExpected(expected)}` : () => - matcherHint(matcherName, receivedName) + + matcherHint(matcherName, received.getMockName()) + '\n\n' + - `Expected ${type} to have returned:\n` + + `Expected mock function to have returned:\n` + ` ${printExpected(expected)}`; return {message, pass}; @@ -245,28 +232,22 @@ const createLastReturnedMatcher = matcherName => ( ) => { ensureMock(received, matcherName); - const receivedIsSpy = isSpy(received); - const type = receivedIsSpy ? 'spy' : 'mock function'; - const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - const returnValues = receivedIsSpy - ? received.returnValues.all().map(x => x.args) - : received.mock.returnValues; - + const returnValues = received.mock.returnValues; const lastReturnValue = returnValues[returnValues.length - 1]; const pass = equals(lastReturnValue, expected, [iterableEquality]); const message = pass ? () => - matcherHint('.not' + matcherName, receivedName) + + matcherHint('.not' + matcherName, received.getMockName()) + '\n\n' + - `Expected ${type} to not have last returned:\n` + + 'Expected mock function to not have last returned:\n' + ` ${printExpected(expected)}` : () => - matcherHint(matcherName, receivedName) + + matcherHint(matcherName, received.getMockName()) + '\n\n' + - `Expected ${type} to have last returned:\n` + + 'Expected mock function to have last returned:\n' + ` ${printExpected(expected)}\n` + - `But it last returned:\n` + + 'But it last returned:\n' + ` ${printReceived(lastReturnValue)}`; return {message, pass}; @@ -323,9 +304,6 @@ const createNthReturnedWithMatcher = (matcherName: string) => ( ) => { ensureMock(received, matcherName); - const receivedIsSpy = isSpy(received); - const type = receivedIsSpy ? 'spy' : 'mock function'; - if (typeof nth !== 'number' || parseInt(nth, 10) !== nth || nth < 1) { const message = () => `nth value ${printReceived( @@ -335,24 +313,19 @@ const createNthReturnedWithMatcher = (matcherName: string) => ( return {message, pass}; } - const receivedName = receivedIsSpy ? 'spy' : received.getMockName(); - const returnValues = receivedIsSpy - ? received.returnValues.all().map(x => x.args) - : received.mock.returnValues; + const returnValues = received.mock.returnValues; const pass = equals(returnValues[nth - 1], expected, [iterableEquality]); - + const nthString = nthToString(nth); const message = pass ? () => - matcherHint('.not' + matcherName, receivedName) + + matcherHint('.not' + matcherName, received.getMockName()) + '\n\n' + - `Expected ${type} ${nthToString( - nth, - )} call to not have returned with:\n` + + `Expected mock function ${nthString} call to not have returned with:\n` + ` ${printExpected(expected)}` : () => - matcherHint(matcherName, receivedName) + + matcherHint(matcherName, received.getMockName()) + '\n\n' + - `Expected ${type} ${nthToString(nth)} call to have returned with:\n` + + `Expected mock function ${nthString} call to have returned with:\n` + ` ${printExpected(expected)}`; return {message, pass}; From c8d094bdb3bd228f8ceeced7d8e15f855dc5c674 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Sun, 22 Apr 2018 15:56:48 -0500 Subject: [PATCH 05/11] Update docs --- docs/ExpectAPI.md | 105 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 9f45608abf1e..ea3749649455 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -649,22 +649,115 @@ test('drinkEach drinks each drink', () => { }); ``` -### `.toHaveReturned(value)` +Note: the nth argument must be positive integer starting from 1. -Also under the alias: `.toReturn(value)` +### `.toHaveReturned()` -If you have a mock function, you can use `.toHaveReturned` to test that the spy -returned a value. For example, let's say you have a mock `drink` that returns +Also under the alias: `.toReturn()` + +If you have a mock function, you can use `.toHaveReturned` to test that the mock +function returned a value. For example, let's say you have a mock `drink` that +returns `true`. You can write: + +```js +test('drinks returns', () => { + const drink = jest.fn(() => true); + + drink(); + + expect(drink).toHaveReturned(); +}); +``` + +### `.toHaveReturnedTimes(number)` + +Also under the alias: `.toReturnTimes(number)` + +Use `.toHaveReturnedTimes` to ensure that a mock function returned an exact +number of times. For example, let's say you have a mock `drink` that returns `true`. You can write: ```js -test('drinks returns true', () => { +test('drink returns twice', () => { const drink = jest.fn(() => true); + + drink(); drink(); - expect(drink).toHaveReturned(true); + + expect(drink).toHaveReturnedTimes(2); +}); +``` + +### `.toHaveReturnedWith(value)` + +Also under the alias: `.toReturnWith(value)` + +Use `.toHaveReturnedWith` to ensure that a mock function returned a specific +value. + +For example, let's say you have a mock `drink` that returns the name of the +beverage that was consumed. You can write: + +```js +test('drink returns La Croix', () => { + const beverage = {name: 'La Croix'}; + const drink = jest.fn(beverage => beverage.name); + + drink(beverage); + + expect(drink).toHaveReturnedWith('La Croix'); +}); +``` + +### `.toHaveLastReturnedWith(value)` + +Also under the alias: `.lastReturnedWith(value)` + +Use `.toHaveLastReturnedWith` to test the specific value that a mock function +last returned. + +For example, let's say you have a mock `drink` that returns the name of the +beverage that was consumed. You can write: + +```js +test('drink returns La Croix (Orange) last', () => { + const beverage1 = {name: 'La Croix (Lemon)'}; + const beverage2 = {name: 'La Croix (Orange)'}; + const drink = jest.fn(beverage => beverage.name); + + drink(beverage1); + drink(beverage2); + + expect(drink).toHaveLastReturnedWith('La Croix (Orange)'); }); ``` +### `.toHaveNthReturnedWith(nthCall, value)` + +Also under the alias: `.nthReturnedWith(nthCall, value)` + +Use `.toHaveNthReturnedWith` to test the specific value that a mock function +returned for the nth call. + +For example, let's say you have a mock `drink` that returns the name of the +beverage that was consumed. You can write: + +```js +test('drink returns expected nth calls', () => { + const beverage1 = {name: 'La Croix (Lemon)'}; + const beverage2 = {name: 'La Croix (Orange)'}; + const drink = jest.fn(beverage => beverage.name); + + drink(beverage1); + drink(beverage2); + + expect(drink).toHaveNthReturnedWith(1, 'La Croix (Lemon)'); + expect(drink).toHaveNthReturnedWith(2, 'La Croix (Orange)'); +}); +``` + +Note: the nth argument must be positive integer starting from 1. + ### `.toBeCloseTo(number, numDigits)` Using exact equality with floating point numbers is a bad idea. Rounding means From 64640a614a5275ab123fa62d19bbc79f9c9dc0ba Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Sun, 22 Apr 2018 16:01:18 -0500 Subject: [PATCH 06/11] rm dupe note --- docs/ExpectAPI.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index ea3749649455..cd01595c8290 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -638,8 +638,6 @@ arguments it was nth called with. For example, let's say you have a flavors, and you want to ensure that when you call it, the first flavor it operates on is `'lemon'` and the second one is `'octopus'`. You can write: -Note that, nth argument must be positive integer starting from 1. - ```js test('drinkEach drinks each drink', () => { const drink = jest.fn(); From 22b688452b52296fb62b8b2b72d4f46698386eb8 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Sun, 29 Apr 2018 14:35:01 -0400 Subject: [PATCH 07/11] Updates based on feedback --- .../__snapshots__/spy_matchers.test.js.snap | 248 ++++++++++++++---- .../expect/src/__tests__/spy_matchers.test.js | 26 ++ packages/expect/src/spy_matchers.js | 68 ++++- 3 files changed, 276 insertions(+), 66 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap index f38d780069a7..f09f6ab73509 100644 --- a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap @@ -131,29 +131,34 @@ exports[`lastReturnedWith works when not called 1`] = ` Expected mock function to have last returned: \\"foo\\" -But it last returned: - undefined" +But it did \\"not return\\"" `; exports[`lastReturnedWith works with Immutable.js objects directly created 1`] = ` "expect(jest.fn()).not.lastReturnedWith(expected) Expected mock function to not have last returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it last returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`lastReturnedWith works with Immutable.js objects indirectly created 1`] = ` "expect(jest.fn()).not.lastReturnedWith(expected) Expected mock function to not have last returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it last returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`lastReturnedWith works with Map 1`] = ` "expect(jest.fn()).not.lastReturnedWith(expected) Expected mock function to not have last returned: - Map {1 => 2, 2 => 1}" + Map {1 => 2, 2 => 1} +But it last returned exactly: + Map {1 => 2, 2 => 1}" `; exports[`lastReturnedWith works with Map 2`] = ` @@ -169,7 +174,9 @@ exports[`lastReturnedWith works with Set 1`] = ` "expect(jest.fn()).not.lastReturnedWith(expected) Expected mock function to not have last returned: - Set {1, 2}" + Set {1, 2} +But it last returned exactly: + Set {1, 2}" `; exports[`lastReturnedWith works with Set 2`] = ` @@ -185,7 +192,9 @@ exports[`lastReturnedWith works with argument that does match 1`] = ` "expect(jest.fn()).not.lastReturnedWith(expected) Expected mock function to not have last returned: - \\"foo\\"" + \\"foo\\" +But it last returned exactly: + \\"foo\\"" `; exports[`lastReturnedWith works with argument that does not match 1`] = ` @@ -332,14 +341,18 @@ exports[`nthReturnedWith should replace 1st, 2nd, 3rd with first, second, third "expect(jest.fn()).nthReturnedWith(expected) Expected mock function first call to have returned with: - \\"bar1\\"" + \\"bar1\\" +But the first call returned with: + \\"foo1\\"" `; exports[`nthReturnedWith should replace 1st, 2nd, 3rd with first, second, third 2`] = ` "expect(jest.fn()).not.nthReturnedWith(expected) Expected mock function first call to not have returned with: - \\"foo1\\"" + \\"foo1\\" +But the first call returned exactly: + \\"foo1\\"" `; exports[`nthReturnedWith works only on spies or jest.fn 1`] = ` @@ -354,70 +367,89 @@ exports[`nthReturnedWith works when not called 1`] = ` "expect(jest.fn()).nthReturnedWith(expected) Expected mock function first call to have returned with: - \\"foo\\"" + \\"foo\\" +But it did not return" `; exports[`nthReturnedWith works with Immutable.js objects directly created 1`] = ` "expect(jest.fn()).not.nthReturnedWith(expected) Expected mock function first call to not have returned with: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But the first call returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`nthReturnedWith works with Immutable.js objects indirectly created 1`] = ` "expect(jest.fn()).not.nthReturnedWith(expected) Expected mock function first call to not have returned with: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But the first call returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`nthReturnedWith works with Map 1`] = ` "expect(jest.fn()).not.nthReturnedWith(expected) Expected mock function first call to not have returned with: - Map {1 => 2, 2 => 1}" + Map {1 => 2, 2 => 1} +But the first call returned exactly: + Map {1 => 2, 2 => 1}" `; exports[`nthReturnedWith works with Map 2`] = ` "expect(jest.fn()).nthReturnedWith(expected) Expected mock function first call to have returned with: - Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"} +But the first call returned with: + Map {1 => 2, 2 => 1}" `; exports[`nthReturnedWith works with Set 1`] = ` "expect(jest.fn()).not.nthReturnedWith(expected) Expected mock function first call to not have returned with: - Set {1, 2}" + Set {1, 2} +But the first call returned exactly: + Set {1, 2}" `; exports[`nthReturnedWith works with Set 2`] = ` "expect(jest.fn()).nthReturnedWith(expected) Expected mock function first call to have returned with: - Set {3, 4}" + Set {3, 4} +But the first call returned with: + Set {1, 2}" `; exports[`nthReturnedWith works with argument that does match 1`] = ` "expect(jest.fn()).not.nthReturnedWith(expected) Expected mock function first call to not have returned with: - \\"foo\\"" + \\"foo\\" +But the first call returned exactly: + \\"foo\\"" `; exports[`nthReturnedWith works with argument that does not match 1`] = ` "expect(jest.fn()).nthReturnedWith(expected) Expected mock function first call to have returned with: - \\"bar\\"" + \\"bar\\" +But the first call returned with: + \\"foo\\"" `; exports[`nthReturnedWith works with three calls 1`] = ` "expect(jest.fn()).not.nthReturnedWith(expected) Expected mock function first call to not have returned with: - \\"foo1\\"" + \\"foo1\\" +But the first call returned exactly: + \\"foo1\\"" `; exports[`toBeCalled .not fails with any argument passed 1`] = ` @@ -1248,29 +1280,34 @@ exports[`toHaveLastReturnedWith works when not called 1`] = ` Expected mock function to have last returned: \\"foo\\" -But it last returned: - undefined" +But it did \\"not return\\"" `; exports[`toHaveLastReturnedWith works with Immutable.js objects directly created 1`] = ` "expect(jest.fn()).not.toHaveLastReturnedWith(expected) Expected mock function to not have last returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it last returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toHaveLastReturnedWith works with Immutable.js objects indirectly created 1`] = ` "expect(jest.fn()).not.toHaveLastReturnedWith(expected) Expected mock function to not have last returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it last returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toHaveLastReturnedWith works with Map 1`] = ` "expect(jest.fn()).not.toHaveLastReturnedWith(expected) Expected mock function to not have last returned: - Map {1 => 2, 2 => 1}" + Map {1 => 2, 2 => 1} +But it last returned exactly: + Map {1 => 2, 2 => 1}" `; exports[`toHaveLastReturnedWith works with Map 2`] = ` @@ -1286,7 +1323,9 @@ exports[`toHaveLastReturnedWith works with Set 1`] = ` "expect(jest.fn()).not.toHaveLastReturnedWith(expected) Expected mock function to not have last returned: - Set {1, 2}" + Set {1, 2} +But it last returned exactly: + Set {1, 2}" `; exports[`toHaveLastReturnedWith works with Set 2`] = ` @@ -1302,7 +1341,9 @@ exports[`toHaveLastReturnedWith works with argument that does match 1`] = ` "expect(jest.fn()).not.toHaveLastReturnedWith(expected) Expected mock function to not have last returned: - \\"foo\\"" + \\"foo\\" +But it last returned exactly: + \\"foo\\"" `; exports[`toHaveLastReturnedWith works with argument that does not match 1`] = ` @@ -1322,14 +1363,18 @@ exports[`toHaveNthReturnedWith should replace 1st, 2nd, 3rd with first, second, "expect(jest.fn()).toHaveNthReturnedWith(expected) Expected mock function first call to have returned with: - \\"bar1\\"" + \\"bar1\\" +But the first call returned with: + \\"foo1\\"" `; exports[`toHaveNthReturnedWith should replace 1st, 2nd, 3rd with first, second, third 2`] = ` "expect(jest.fn()).not.toHaveNthReturnedWith(expected) Expected mock function first call to not have returned with: - \\"foo1\\"" + \\"foo1\\" +But the first call returned exactly: + \\"foo1\\"" `; exports[`toHaveNthReturnedWith works only on spies or jest.fn 1`] = ` @@ -1344,70 +1389,89 @@ exports[`toHaveNthReturnedWith works when not called 1`] = ` "expect(jest.fn()).toHaveNthReturnedWith(expected) Expected mock function first call to have returned with: - \\"foo\\"" + \\"foo\\" +But it did not return" `; exports[`toHaveNthReturnedWith works with Immutable.js objects directly created 1`] = ` "expect(jest.fn()).not.toHaveNthReturnedWith(expected) Expected mock function first call to not have returned with: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But the first call returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toHaveNthReturnedWith works with Immutable.js objects indirectly created 1`] = ` "expect(jest.fn()).not.toHaveNthReturnedWith(expected) Expected mock function first call to not have returned with: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But the first call returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toHaveNthReturnedWith works with Map 1`] = ` "expect(jest.fn()).not.toHaveNthReturnedWith(expected) Expected mock function first call to not have returned with: - Map {1 => 2, 2 => 1}" + Map {1 => 2, 2 => 1} +But the first call returned exactly: + Map {1 => 2, 2 => 1}" `; exports[`toHaveNthReturnedWith works with Map 2`] = ` "expect(jest.fn()).toHaveNthReturnedWith(expected) Expected mock function first call to have returned with: - Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"} +But the first call returned with: + Map {1 => 2, 2 => 1}" `; exports[`toHaveNthReturnedWith works with Set 1`] = ` "expect(jest.fn()).not.toHaveNthReturnedWith(expected) Expected mock function first call to not have returned with: - Set {1, 2}" + Set {1, 2} +But the first call returned exactly: + Set {1, 2}" `; exports[`toHaveNthReturnedWith works with Set 2`] = ` "expect(jest.fn()).toHaveNthReturnedWith(expected) Expected mock function first call to have returned with: - Set {3, 4}" + Set {3, 4} +But the first call returned with: + Set {1, 2}" `; exports[`toHaveNthReturnedWith works with argument that does match 1`] = ` "expect(jest.fn()).not.toHaveNthReturnedWith(expected) Expected mock function first call to not have returned with: - \\"foo\\"" + \\"foo\\" +But the first call returned exactly: + \\"foo\\"" `; exports[`toHaveNthReturnedWith works with argument that does not match 1`] = ` "expect(jest.fn()).toHaveNthReturnedWith(expected) Expected mock function first call to have returned with: - \\"bar\\"" + \\"bar\\" +But the first call returned with: + \\"foo\\"" `; exports[`toHaveNthReturnedWith works with three calls 1`] = ` "expect(jest.fn()).not.toHaveNthReturnedWith(expected) Expected mock function first call to not have returned with: - \\"foo1\\"" + \\"foo1\\" +But the first call returned exactly: + \\"foo1\\"" `; exports[`toHaveReturned .not fails with any argument passed 1`] = ` @@ -1581,63 +1645,99 @@ exports[`toHaveReturnedWith works when not called 1`] = ` "expect(jest.fn()).toHaveReturnedWith(expected) Expected mock function to have returned: - \\"foo\\"" + \\"foo\\" +But it did not return." `; exports[`toHaveReturnedWith works with Immutable.js objects directly created 1`] = ` "expect(jest.fn()).not.toHaveReturnedWith(expected) Expected mock function not to have returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toHaveReturnedWith works with Immutable.js objects indirectly created 1`] = ` "expect(jest.fn()).not.toHaveReturnedWith(expected) Expected mock function not to have returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toHaveReturnedWith works with Map 1`] = ` "expect(jest.fn()).not.toHaveReturnedWith(expected) Expected mock function not to have returned: - Map {1 => 2, 2 => 1}" + Map {1 => 2, 2 => 1} +But it returned exactly: + Map {1 => 2, 2 => 1}" `; exports[`toHaveReturnedWith works with Map 2`] = ` "expect(jest.fn()).toHaveReturnedWith(expected) Expected mock function to have returned: - Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"} +But it returned: + Map {1 => 2, 2 => 1}" `; exports[`toHaveReturnedWith works with Set 1`] = ` "expect(jest.fn()).not.toHaveReturnedWith(expected) Expected mock function not to have returned: - Set {1, 2}" + Set {1, 2} +But it returned exactly: + Set {1, 2}" `; exports[`toHaveReturnedWith works with Set 2`] = ` "expect(jest.fn()).toHaveReturnedWith(expected) Expected mock function to have returned: - Set {3, 4}" + Set {3, 4} +But it returned: + Set {1, 2}" `; exports[`toHaveReturnedWith works with argument that does match 1`] = ` "expect(jest.fn()).not.toHaveReturnedWith(expected) Expected mock function not to have returned: - \\"foo\\"" + \\"foo\\" +But it returned exactly: + \\"foo\\"" `; exports[`toHaveReturnedWith works with argument that does not match 1`] = ` "expect(jest.fn()).toHaveReturnedWith(expected) Expected mock function to have returned: - \\"bar\\"" + \\"bar\\" +But it returned: + \\"foo\\"" +`; + +exports[`toHaveReturnedWith works with more calls than the limit 1`] = ` +"expect(jest.fn()).toHaveReturnedWith(expected) + +Expected mock function to have returned: + \\"bar\\" +But it returned: + \\"foo1\\" + + \\"foo2\\" + + \\"foo3\\" + + \\"foo4\\" + + \\"foo5\\" + + ...and 1 more" `; exports[`toReturn .not fails with any argument passed 1`] = ` @@ -1811,61 +1911,97 @@ exports[`toReturnWith works when not called 1`] = ` "expect(jest.fn()).toReturnWith(expected) Expected mock function to have returned: - \\"foo\\"" + \\"foo\\" +But it did not return." `; exports[`toReturnWith works with Immutable.js objects directly created 1`] = ` "expect(jest.fn()).not.toReturnWith(expected) Expected mock function not to have returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toReturnWith works with Immutable.js objects indirectly created 1`] = ` "expect(jest.fn()).not.toReturnWith(expected) Expected mock function not to have returned: - Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}} +But it returned exactly: + Immutable.Map {\\"a\\": {\\"b\\": \\"c\\"}}" `; exports[`toReturnWith works with Map 1`] = ` "expect(jest.fn()).not.toReturnWith(expected) Expected mock function not to have returned: - Map {1 => 2, 2 => 1}" + Map {1 => 2, 2 => 1} +But it returned exactly: + Map {1 => 2, 2 => 1}" `; exports[`toReturnWith works with Map 2`] = ` "expect(jest.fn()).toReturnWith(expected) Expected mock function to have returned: - Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"}" + Map {\\"a\\" => \\"b\\", \\"b\\" => \\"a\\"} +But it returned: + Map {1 => 2, 2 => 1}" `; exports[`toReturnWith works with Set 1`] = ` "expect(jest.fn()).not.toReturnWith(expected) Expected mock function not to have returned: - Set {1, 2}" + Set {1, 2} +But it returned exactly: + Set {1, 2}" `; exports[`toReturnWith works with Set 2`] = ` "expect(jest.fn()).toReturnWith(expected) Expected mock function to have returned: - Set {3, 4}" + Set {3, 4} +But it returned: + Set {1, 2}" `; exports[`toReturnWith works with argument that does match 1`] = ` "expect(jest.fn()).not.toReturnWith(expected) Expected mock function not to have returned: - \\"foo\\"" + \\"foo\\" +But it returned exactly: + \\"foo\\"" `; exports[`toReturnWith works with argument that does not match 1`] = ` "expect(jest.fn()).toReturnWith(expected) Expected mock function to have returned: - \\"bar\\"" + \\"bar\\" +But it returned: + \\"foo\\"" +`; + +exports[`toReturnWith works with more calls than the limit 1`] = ` +"expect(jest.fn()).toReturnWith(expected) + +Expected mock function to have returned: + \\"bar\\" +But it returned: + \\"foo1\\" + + \\"foo2\\" + + \\"foo3\\" + + \\"foo4\\" + + \\"foo5\\" + + ...and 1 more" `; diff --git a/packages/expect/src/__tests__/spy_matchers.test.js b/packages/expect/src/__tests__/spy_matchers.test.js index be9f927d7be3..0230d5115a24 100644 --- a/packages/expect/src/__tests__/spy_matchers.test.js +++ b/packages/expect/src/__tests__/spy_matchers.test.js @@ -586,6 +586,32 @@ const jestExpect = require('../'); ).toThrowErrorMatchingSnapshot(); }); + const basicReturnedWith = ['toHaveReturnedWith', 'toReturnWith']; + if (basicReturnedWith.indexOf(returnedWith) >= 0) { + test(`works with more calls than the limit`, () => { + const fn = jest.fn(); + fn.mockReturnValueOnce('foo1'); + fn.mockReturnValueOnce('foo2'); + fn.mockReturnValueOnce('foo3'); + fn.mockReturnValueOnce('foo4'); + fn.mockReturnValueOnce('foo5'); + fn.mockReturnValueOnce('foo6'); + + fn(); + fn(); + fn(); + fn(); + fn(); + fn(); + + jestExpect(fn).not[returnedWith]('bar'); + + expect(() => { + jestExpect(fn)[returnedWith]('bar'); + }).toThrowErrorMatchingSnapshot(); + }); + } + const nthCalled = ['toHaveNthReturnedWith', 'nthReturnedWith']; if (nthCalled.indexOf(returnedWith) >= 0) { test(`works with three calls`, () => { diff --git a/packages/expect/src/spy_matchers.js b/packages/expect/src/spy_matchers.js index 708f879da06f..b311d35c4306 100644 --- a/packages/expect/src/spy_matchers.js +++ b/packages/expect/src/spy_matchers.js @@ -10,6 +10,7 @@ import type {MatchersObject} from 'types/Matchers'; const CALL_PRINT_LIMIT = 3; +const RETURN_PRINT_LIMIT = 5; const LAST_CALL_PRINT_LIMIT = 1; import { ensureExpectedIsNumber, @@ -58,7 +59,8 @@ const createToReturnMatcher = matcherName => (received, expected) => { ensureNoExpected(expected, matcherName); ensureMock(received, matcherName); - const count = received.mock.returnValues.length; + const returnValues = received.mock.returnValues; + const count = returnValues.length; const pass = count > 0; const message = pass @@ -66,7 +68,7 @@ const createToReturnMatcher = matcherName => (received, expected) => { matcherHint('.not' + matcherName, received.getMockName(), '') + '\n\n' + `Expected mock function not to have returned, but it returned:\n` + - ` ${RECEIVED_COLOR(received.mock.returnValues.join(', '))}` + ` ${getPrintedReturnValues(returnValues, RETURN_PRINT_LIMIT)}` : () => matcherHint(matcherName, received.getMockName(), '') + '\n\n' + @@ -187,12 +189,18 @@ const createToReturnWithMatcher = matcherName => ( matcherHint('.not' + matcherName, received.getMockName()) + '\n\n' + `Expected mock function not to have returned:\n` + - ` ${printExpected(expected)}` + ` ${printExpected(expected)}\n` + + `But it returned exactly:\n` + + ` ${printReceived(expected)}` : () => matcherHint(matcherName, received.getMockName()) + '\n\n' + `Expected mock function to have returned:\n` + - ` ${printExpected(expected)}`; + formatMismatchedReturnValues( + returnValues, + expected, + RETURN_PRINT_LIMIT, + ); return {message, pass}; }; @@ -241,14 +249,17 @@ const createLastReturnedMatcher = matcherName => ( matcherHint('.not' + matcherName, received.getMockName()) + '\n\n' + 'Expected mock function to not have last returned:\n' + - ` ${printExpected(expected)}` + ` ${printExpected(expected)}\n` + + `But it last returned exactly:\n` + + ` ${printReceived(lastReturnValue)}` : () => matcherHint(matcherName, received.getMockName()) + '\n\n' + 'Expected mock function to have last returned:\n' + ` ${printExpected(expected)}\n` + - 'But it last returned:\n' + - ` ${printReceived(lastReturnValue)}`; + (returnValues.length > 0 + ? `But it last returned:\n ${printReceived(lastReturnValue)}` + : `But it did ${printReceived('not return')}`); return {message, pass}; }; @@ -314,19 +325,27 @@ const createNthReturnedWithMatcher = (matcherName: string) => ( } const returnValues = received.mock.returnValues; - const pass = equals(returnValues[nth - 1], expected, [iterableEquality]); + const nthValue = returnValues[nth - 1]; + const pass = equals(nthValue, expected, [iterableEquality]); const nthString = nthToString(nth); const message = pass ? () => matcherHint('.not' + matcherName, received.getMockName()) + '\n\n' + `Expected mock function ${nthString} call to not have returned with:\n` + - ` ${printExpected(expected)}` + ` ${printExpected(expected)}\n` + + `But the ${nthString} call returned exactly:\n` + + ` ${printReceived(nthValue)}` : () => matcherHint(matcherName, received.getMockName()) + '\n\n' + `Expected mock function ${nthString} call to have returned with:\n` + - ` ${printExpected(expected)}`; + ` ${printExpected(expected)}\n` + + (returnValues.length > 0 + ? `But the ${nthString} call returned with:\n ${printReceived( + nthValue, + )}` + : `But it did ${RECEIVED_COLOR('not return')}`); return {message, pass}; }; @@ -392,6 +411,20 @@ const getPrintedCalls = ( return result.join(sep); }; +const getPrintedReturnValues = (calls: any[], limit: number): string => { + const result = []; + + for (let i = 0; i < calls.length && i < limit; i += 1) { + result.push(printReceived(calls[i])); + } + + if (calls.length > limit) { + result.push(`...and ${printReceived(calls.length - limit)} more`); + } + + return result.join('\n\n '); +}; + const formatReceivedCalls = (calls, limit, options) => { if (calls.length) { const but = options && options.sameSentence ? 'but' : 'But'; @@ -426,6 +459,21 @@ const formatMismatchedCalls = (calls, expected, limit) => { } }; +const formatMismatchedReturnValues = (returnValues, expected, limit) => { + if (returnValues.length) { + return ( + ` ${printExpected(expected)}\n` + + `But it returned:\n` + + ` ${getPrintedReturnValues(returnValues, limit)}` + ); + } else { + return ( + ` ${printExpected(expected)}\n` + + `But it did ${RECEIVED_COLOR('not return')}.` + ); + } +}; + const formatMismatchedArgs = (expected, received) => { const length = Math.max(expected.length, received.length); From 3603150e69267a6d57138c95945d1ba9e5a709b6 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Sun, 29 Apr 2018 14:37:14 -0400 Subject: [PATCH 08/11] Remove quotes --- .../src/__tests__/__snapshots__/spy_matchers.test.js.snap | 4 ++-- packages/expect/src/spy_matchers.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap index f09f6ab73509..824c66831bb2 100644 --- a/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap @@ -131,7 +131,7 @@ exports[`lastReturnedWith works when not called 1`] = ` Expected mock function to have last returned: \\"foo\\" -But it did \\"not return\\"" +But it did not return" `; exports[`lastReturnedWith works with Immutable.js objects directly created 1`] = ` @@ -1280,7 +1280,7 @@ exports[`toHaveLastReturnedWith works when not called 1`] = ` Expected mock function to have last returned: \\"foo\\" -But it did \\"not return\\"" +But it did not return" `; exports[`toHaveLastReturnedWith works with Immutable.js objects directly created 1`] = ` diff --git a/packages/expect/src/spy_matchers.js b/packages/expect/src/spy_matchers.js index b311d35c4306..9d201face7b8 100644 --- a/packages/expect/src/spy_matchers.js +++ b/packages/expect/src/spy_matchers.js @@ -259,7 +259,7 @@ const createLastReturnedMatcher = matcherName => ( ` ${printExpected(expected)}\n` + (returnValues.length > 0 ? `But it last returned:\n ${printReceived(lastReturnValue)}` - : `But it did ${printReceived('not return')}`); + : `But it did ${RECEIVED_COLOR('not return')}`); return {message, pass}; }; From c722a08e31db5df11706f7a1c266ace962bde7e6 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Sun, 29 Apr 2018 14:43:06 -0400 Subject: [PATCH 09/11] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 862db3c3d283..a38a6a0d8f08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ ([#5826](https://github.com/facebook/jest/pull/5826)) * `[jest-cli]` Interactive Snapshot Mode improvements ([#5864](https://github.com/facebook/jest/pull/5864)) +* `[expect]` Add return matchers + ([#5879](https://github.com/facebook/jest/pull/5879)) ### Fixes From 19bbe7c4456c3a7ad227104dcb65c7155bb30aac Mon Sep 17 00:00:00 2001 From: Rick Hanlon II Date: Sun, 29 Apr 2018 14:51:14 -0400 Subject: [PATCH 10/11] Revert this --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ff88180f55..88d88493e138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -903,7 +903,7 @@ * Fix issue mocking bound method ([#3805](https://github.com/facebook/jest/pull/3805)) * Fix jest-circus ([#4290](https://github.com/facebook/jest/pull/4290)) -* Fix lint warning in +* Fix lint warning in master ([#4132](https://github.com/facebook/jest/pull/4132)) * Fix linting ([#3946](https://github.com/facebook/jest/pull/3946)) From e8a669a53e024a42d29456f58cab2f0540d8cc01 Mon Sep 17 00:00:00 2001 From: rickhanlonii Date: Sun, 29 Apr 2018 14:52:25 -0400 Subject: [PATCH 11/11] Lint md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d88493e138..34f24117bee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -906,6 +906,7 @@ * Fix lint warning in master ([#4132](https://github.com/facebook/jest/pull/4132)) + * Fix linting ([#3946](https://github.com/facebook/jest/pull/3946)) * fix merge conflict ([#4144](https://github.com/facebook/jest/pull/4144)) * Fix minor typo ([#3729](https://github.com/facebook/jest/pull/3729))