diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d653977e0f7..34f24117bee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,8 @@ ([#5889](https://github.com/facebook/jest/pull/5889)) * `[expect]` Introduce toStrictEqual ([#6032](https://github.com/facebook/jest/pull/6032)) +* `[expect]` Add return matchers + ([#5879](https://github.com/facebook/jest/pull/5879)) ### Fixes @@ -902,7 +904,9 @@ ([#3805](https://github.com/facebook/jest/pull/3805)) * Fix jest-circus ([#4290](https://github.com/facebook/jest/pull/4290)) * 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)) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 50b87353188e..c818cdbd828c 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -667,8 +667,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(); @@ -678,6 +676,115 @@ test('drinkEach drinks each drink', () => { }); ``` +Note: the nth argument must be positive integer starting from 1. + +### `.toHaveReturned()` + +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('drink returns twice', () => { + const drink = jest.fn(() => true); + + drink(); + drink(); + + 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 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 ec34e5010e26..6499d917ff93 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,94 @@ 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 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\\"}} +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\\"}} +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} +But it last returned exactly: + 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} +But it last returned exactly: + 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\\" +But it last returned exactly: + \\"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 +333,125 @@ 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\\" +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\\" +But the first call returned exactly: + \\"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\\" +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\\"}} +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\\"}} +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} +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\\"} +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} +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} +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\\" +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\\" +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\\" +But the first call returned exactly: + \\"foo1\\"" +`; + exports[`toBeCalled .not fails with any argument passed 1`] = ` "expect(received)[.not].toBeCalled() @@ -1059,3 +1266,742 @@ 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[`toHaveLastReturnedWith works only on spies or jest.fn 1`] = ` +"expect(jest.fn())[.not].toHaveLastReturnedWith() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +exports[`toHaveLastReturnedWith works when not called 1`] = ` +"expect(jest.fn()).toHaveLastReturnedWith(expected) + +Expected mock function to have last returned: + \\"foo\\" +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\\"}} +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\\"}} +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} +But it last returned exactly: + 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} +But it last returned exactly: + 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\\" +But it last returned exactly: + \\"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\\" +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\\" +But the first call returned exactly: + \\"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\\" +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\\"}} +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\\"}} +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} +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\\"} +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} +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} +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\\" +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\\" +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\\" +But the first call returned exactly: + \\"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`] = ` +"expect(jest.fn())[.not].toHaveReturned() + +jest.fn() value must be a mock function or spy. +Received: + function: [Function fn]" +`; + +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: + \\"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\\"}} +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\\"}} +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} +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\\"} +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} +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} +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\\" +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\\" +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`] = ` +"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`] = ` +"expect(jest.fn())[.not].toReturn() + +jest.fn() value must be a mock function or spy. +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\\" +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\\"}} +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\\"}} +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} +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\\"} +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} +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} +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\\" +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\\" +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 91624ef28515..0230d5115a24 100644 --- a/packages/expect/src/__tests__/spy_matchers.test.js +++ b/packages/expect/src/__tests__/spy_matchers.test.js @@ -342,3 +342,337 @@ const jestExpect = require('../'); } }); }); + +['toReturn', 'toHaveReturned'].forEach(returned => { + describe(`${returned}`, () => { + test(`works only on spies or jest.fn`, () => { + const fn = function fn() {}; + + 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(`fails with any argument passed`, () => { + const fn = jest.fn(); + + fn(); + expect(() => + jestExpect(fn)[returned](555), + ).toThrowErrorMatchingSnapshot(); + }); + + 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)[returnedTimes](1); + + [{}, [], true, 'a', new Map(), () => {}].forEach(value => { + expect(() => + jestExpect(fn)[returnedTimes](value), + ).toThrowErrorMatchingSnapshot(); + }); + }); + + 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)[returnedTimes](2); + + expect(() => + jestExpect(fn).not[returnedTimes](2), + ).toThrowErrorMatchingSnapshot(); + }); + + 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)[returnedTimes](1); + jestExpect(fn).not[returnedTimes](2); + + expect(() => + jestExpect(fn)[returnedTimes](2), + ).toThrowErrorMatchingSnapshot(); + }); + }); +}); + +[ + 'lastReturnedWith', + 'toHaveLastReturnedWith', + 'nthReturnedWith', + 'toHaveNthReturnedWith', + 'toReturnWith', + 'toHaveReturnedWith', +].forEach(returnedWith => { + const caller = function(callee, ...args) { + if ( + returnedWith === 'nthReturnedWith' || + returnedWith === 'toHaveNthReturnedWith' + ) { + callee(1, ...args); + } else { + callee(...args); + } + }; + + describe(`${returnedWith}`, () => { + test(`works only on spies or jest.fn`, () => { + const fn = function fn() {}; + + expect(() => + jestExpect(fn)[returnedWith](), + ).toThrowErrorMatchingSnapshot(); + }); + + test(`works when not called`, () => { + const fn = jest.fn(); + caller(jestExpect(fn).not[returnedWith], 'foo'); + + expect(() => + caller(jestExpect(fn)[returnedWith], 'foo'), + ).toThrowErrorMatchingSnapshot(); + }); + + 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 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`, () => { + 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 1b9c44fddfe0..3a430b7aaa39 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, @@ -54,6 +55,28 @@ const createToBeCalledMatcher = matcherName => (received, expected) => { return {message, pass}; }; +const createToReturnMatcher = matcherName => (received, expected) => { + ensureNoExpected(expected, matcherName); + ensureMock(received, matcherName); + + const returnValues = received.mock.returnValues; + const count = returnValues.length; + const pass = count > 0; + + const message = pass + ? () => + matcherHint('.not' + matcherName, received.getMockName(), '') + + '\n\n' + + `Expected mock function not to have returned, but it returned:\n` + + ` ${getPrintedReturnValues(returnValues, RETURN_PRINT_LIMIT)}` + : () => + matcherHint(matcherName, received.getMockName(), '') + + '\n\n' + + `Expected mock function to have returned.`; + + return {message, pass}; +}; + const createToBeCalledTimesMatcher = (matcherName: string) => ( received: any, expected: number, @@ -85,6 +108,37 @@ const createToBeCalledTimesMatcher = (matcherName: string) => ( return {message, pass}; }; +const createToReturnTimesMatcher = (matcherName: string) => ( + received: any, + expected: number, +) => { + ensureExpectedIsNumber(expected, matcherName); + ensureMock(received, matcherName); + + const count = received.mock.returnValues.length; + const pass = count === expected; + + const message = pass + ? () => + matcherHint( + '.not' + matcherName, + received.getMockName(), + String(expected), + ) + + `\n\n` + + `Expected mock function not to have returned ` + + `${EXPECTED_COLOR(pluralize('time', expected))}, but it` + + ` returned exactly ${RECEIVED_COLOR(pluralize('time', count))}.` + : () => + matcherHint(matcherName, received.getMockName(), String(expected)) + + '\n\n' + + `Expected mock function 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,6 +172,39 @@ const createToBeCalledWithMatcher = matcherName => ( return {message, pass}; }; +const createToReturnWithMatcher = matcherName => ( + received: any, + expected: any, +) => { + ensureMock(received, matcherName); + + const returnValues = received.mock.returnValues; + const [match] = partition(returnValues, value => + equals(expected, value, [iterableEquality]), + ); + const pass = match.length > 0; + + const message = pass + ? () => + matcherHint('.not' + matcherName, received.getMockName()) + + '\n\n' + + `Expected mock function not to have returned:\n` + + ` ${printExpected(expected)}\n` + + `But it returned exactly:\n` + + ` ${printReceived(expected)}` + : () => + matcherHint(matcherName, received.getMockName()) + + '\n\n' + + `Expected mock function to have returned:\n` + + formatMismatchedReturnValues( + returnValues, + expected, + RETURN_PRINT_LIMIT, + ); + + return {message, pass}; +}; + const createLastCalledWithMatcher = matcherName => ( received: any, ...expected: any @@ -147,6 +234,36 @@ const createLastCalledWithMatcher = matcherName => ( return {message, pass}; }; +const createLastReturnedMatcher = matcherName => ( + received: any, + expected: any, +) => { + ensureMock(received, matcherName); + + const returnValues = received.mock.returnValues; + const lastReturnValue = returnValues[returnValues.length - 1]; + const pass = equals(lastReturnValue, expected, [iterableEquality]); + + const message = pass + ? () => + matcherHint('.not' + matcherName, received.getMockName()) + + '\n\n' + + 'Expected mock function to not have last returned:\n' + + ` ${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` + + (returnValues.length > 0 + ? `But it last returned:\n ${printReceived(lastReturnValue)}` + : `But it did ${RECEIVED_COLOR('not return')}`); + + return {message, pass}; +}; + const createNthCalledWithMatcher = (matcherName: string) => ( received: any, nth: number, @@ -191,9 +308,53 @@ const createNthCalledWithMatcher = (matcherName: string) => ( return {message, pass}; }; +const createNthReturnedWithMatcher = (matcherName: string) => ( + received: any, + nth: number, + expected: any, +) => { + ensureMock(received, matcherName); + + 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 returnValues = received.mock.returnValues; + 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)}\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)}\n` + + (returnValues.length > 0 + ? `But the ${nthString} call returned with:\n ${printReceived( + nthValue, + )}` + : `But it did ${RECEIVED_COLOR('not return')}`); + + 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'), @@ -206,6 +367,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'; @@ -242,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'; @@ -276,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);