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';