From b5096b019aa7292e45c8d127e42e5f22e5dbaf5c Mon Sep 17 00:00:00 2001 From: Thibaut Sabot Date: Wed, 23 Mar 2022 10:56:04 +0100 Subject: [PATCH] Add toHaveBeenCalledOnceWith matcher --- README.md | 15 +++++ src/matchers/index.js | 1 + src/matchers/toHaveBeenCalledOnceWith.js | 47 ++++++++++++++ .../toHaveBeenCalledOnceWith.test.js.snap | 37 +++++++++++ .../matchers/toHaveBeenCalledOnceWith.test.js | 62 +++++++++++++++++++ types/index.d.ts | 10 +++ 6 files changed, 172 insertions(+) create mode 100644 src/matchers/toHaveBeenCalledOnceWith.js create mode 100644 test/matchers/__snapshots__/toHaveBeenCalledOnceWith.test.js.snap create mode 100644 test/matchers/toHaveBeenCalledOnceWith.test.js diff --git a/README.md b/README.md index fbc09b17..e02ed837 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ If you've come here to help contribute - Thanks! Take a look at the [contributin - [.toHaveBeenCalledBefore()](#tohavebeencalledbefore) - [.toHaveBeenCalledAfter()](#tohavebeencalledafter) - [.toHaveBeenCalledOnce()](#tohavebeencalledonce) + - [.toHaveBeenCalledOnceWith()](#tohavebeencalledoncewith) - [Number](#number) - [.toBeNumber()](#tobenumber) - [.toBeNaN()](#tobenan) @@ -613,6 +614,20 @@ it('passes only if mock was called exactly once', () => { }); ``` +#### .toHaveBeenCalledOnceWith() + +Use `.toHaveBeenCalledOnceWith` to check if a `Mock` was called exactly one time with the expected value. + +```js +it('passes only if mock was called exactly once with the expected value', () => { + const mock = jest.fn(); + + expect(mock).not.toHaveBeenCalled(); + mock('hello'); + expect(mock).toHaveBeenCalledOnceWith('hello'); +}); +``` + ### Number #### .toBeNumber() diff --git a/src/matchers/index.js b/src/matchers/index.js index d2fdad5b..4c607c3b 100644 --- a/src/matchers/index.js +++ b/src/matchers/index.js @@ -51,6 +51,7 @@ export { toEqualCaseInsensitive } from './toEqualCaseInsensitive'; export { toHaveBeenCalledAfter } from './toHaveBeenCalledAfter'; export { toHaveBeenCalledBefore } from './toHaveBeenCalledBefore'; export { toHaveBeenCalledOnce } from './toHaveBeenCalledOnce'; +export { toHaveBeenCalledOnceWith } from './toHaveBeenCalledOnceWith'; export { toInclude } from './toInclude'; export { toIncludeAllMembers } from './toIncludeAllMembers'; export { toIncludeAllPartialMembers } from './toIncludeAllPartialMembers'; diff --git a/src/matchers/toHaveBeenCalledOnceWith.js b/src/matchers/toHaveBeenCalledOnceWith.js new file mode 100644 index 00000000..baf1fbbb --- /dev/null +++ b/src/matchers/toHaveBeenCalledOnceWith.js @@ -0,0 +1,47 @@ +import { isJestMockOrSpy } from '../utils'; + +export function toHaveBeenCalledOnceWith(received, expected) { + const { printReceived, printExpected, printWithType, matcherHint } = this.utils; + + if (!isJestMockOrSpy(received)) { + return { + pass: false, + message: () => + matcherHint('.toHaveBeenCalledOnceWith') + + '\n\n' + + `Matcher error: ${printReceived('received')} must be a mock or spy function` + + '\n\n' + + printWithType('Received', received, printReceived), + }; + } + + const passMessage = + matcherHint('.not.toHaveBeenCalledOnceWith') + + '\n\n' + + `Expected mock function to have been called any amount of times but one with ${printExpected( + expected, + )}, but it was called exactly once with ${printExpected(expected)}.`; + + const failOnceMessage = + matcherHint('.toHaveBeenCalledOnceWith') + + '\n\n' + + 'Expected mock function to have been called exactly once, but it was called:\n' + + ` ${printReceived(received.mock.calls.length)} times`; + + const failExpectedMessage = + matcherHint('.toHaveBeenCalledOnceWith') + + '\n\n' + + `Expected mock function to have been called exactly once with ${printReceived( + expected, + )}, but it was called with:\n` + + ` ${printReceived(received.mock.calls[0]?.[0])}`; + + const passOnce = received.mock.calls.length === 1; + const pass = passOnce && this.equals(expected, received.mock.calls[0][0]); + + return { + pass, + message: () => (pass ? passMessage : !passOnce ? failOnceMessage : failExpectedMessage), + actual: received, + }; +} diff --git a/test/matchers/__snapshots__/toHaveBeenCalledOnceWith.test.js.snap b/test/matchers/__snapshots__/toHaveBeenCalledOnceWith.test.js.snap new file mode 100644 index 00000000..8ea93849 --- /dev/null +++ b/test/matchers/__snapshots__/toHaveBeenCalledOnceWith.test.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`.not.toHaveBeenCalledOnceWith fails if mock was invoked exactly once with the expected value 1`] = ` +"expect(received).not.toHaveBeenCalledOnceWith(expected) + +Expected mock function to have been called any amount of times but one with \\"hello\\", but it was called exactly once with \\"hello\\"." +`; + +exports[`.toHaveBeenCalledOnceWith fails if mock was invoked more than once, indicating how many times it was invoked 1`] = ` +"expect(received).toHaveBeenCalledOnceWith(expected) + +Expected mock function to have been called exactly once, but it was called: + 17 times" +`; + +exports[`.toHaveBeenCalledOnceWith fails if mock was never invoked indicating that it was invoked 0 times 1`] = ` +"expect(received).toHaveBeenCalledOnceWith(expected) + +Expected mock function to have been called exactly once, but it was called: + 0 times" +`; + +exports[`.toHaveBeenCalledOnceWith fails when given value is not a jest spy or mock 1`] = ` +"expect(received).toHaveBeenCalledOnceWith(expected) + +Matcher error: \\"received\\" must be a mock or spy function + +Received has type: function +Received has value: [Function mock1]" +`; + +exports[`.toHaveBeenCalledOnceWith fails when given value is not the expected one 1`] = ` +"expect(received).toHaveBeenCalledOnceWith(expected) + +Expected mock function to have been called exactly once with \\"hello\\", but it was called with: + \\"not hello\\"" +`; diff --git a/test/matchers/toHaveBeenCalledOnceWith.test.js b/test/matchers/toHaveBeenCalledOnceWith.test.js new file mode 100644 index 00000000..23c3ac4f --- /dev/null +++ b/test/matchers/toHaveBeenCalledOnceWith.test.js @@ -0,0 +1,62 @@ +import * as matcher from 'src/matchers/toHaveBeenCalledOnceWith'; + +expect.extend(matcher); + +describe('.toHaveBeenCalledOnceWith', () => { + let mock; + beforeEach(() => { + mock = jest.fn(); + }); + + test('passes if mock was invoked exactly once with the expected value', () => { + mock('hello'); + expect(mock).toHaveBeenCalledOnceWith('hello'); + }); + + test('fails if mock was never invoked indicating that it was invoked 0 times', () => { + expect(() => expect(mock).toHaveBeenCalledOnceWith('hello')).toThrowErrorMatchingSnapshot(); + }); + + test('fails if mock was invoked more than once, indicating how many times it was invoked', () => { + // Invoke mock 17 times + new Array(17).fill(mock).forEach(e => e(Math.random())); + expect(() => expect(mock).toHaveBeenCalledOnceWith('hello')).toThrowErrorMatchingSnapshot(); + }); + + test('fails when given value is not a jest spy or mock', () => { + const mock1 = () => {}; + expect(() => expect(mock1).toHaveBeenCalledOnceWith('hello')).toThrowErrorMatchingSnapshot(); + }); + + test('fails when given value is not the expected one', () => { + mock('not hello'); + expect(() => expect(mock).toHaveBeenCalledOnceWith('hello')).toThrowErrorMatchingSnapshot(); + }); +}); + +describe('.not.toHaveBeenCalledOnceWith', () => { + let mock; + beforeEach(() => { + mock = jest.fn(); + }); + + test('passes if mock was never invoked', () => { + expect(mock).not.toHaveBeenCalledOnceWith('hello'); + }); + + test('passes if mock was invoked more than once', () => { + mock('hello'); + mock('hello'); + expect(mock).not.toHaveBeenCalledOnceWith('hello'); + }); + + test('fails if mock was invoked exactly once with the expected value', () => { + mock('hello'); + expect(() => expect(mock).not.toHaveBeenCalledOnceWith('hello')).toThrowErrorMatchingSnapshot(); + }); + + test('passes if mock was invoked exactly once without the expected value', () => { + mock('not hello'); + expect(mock).not.toHaveBeenCalledOnceWith('hello'); + }); +}); diff --git a/types/index.d.ts b/types/index.d.ts index 8240dc37..107c65b3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -171,6 +171,11 @@ declare namespace jest { */ toHaveBeenCalledOnce(): R; + /** + * Use `.toHaveBeenCalledOnceWith` to check if a `Mock` was called exactly one time with the expected value. + */ + toHaveBeenCalledOnceWith(): R; + /** * Use `.toBeNumber` when checking if a value is a `Number`. */ @@ -601,6 +606,11 @@ declare namespace jest { */ toHaveBeenCalledOnce(): Result; + /** + * Use `.toHaveBeenCalledOnceWith` to check if a `Mock` was called exactly one time with the expected value. + */ + toHaveBeenCalledOnceWith(): Result; + /** * Use `.toBeNumber` when checking if a value is a `Number`. */