168

I have the following function in a React component:

onUploadStart(file, xhr, formData) {
  formData.append('filename', file.name);
  formData.append('mimeType', file.type);
}

This is my test that at least gets the spy to be called:

const formData = { append: jest.fn() };
const file = { name: 'someFileName', type: 'someMimeType' };
eventHandlers.onUploadStart(file, null, formData);

expect(formData.append).toHaveBeenCalledWith(
  ['mimeType', 'someMimeType'],
  ['fileName', 'someFileName']
);

However, the assertion is not working:

Expected mock function to have been called with:
 [["mimeType", "someMimeType"], ["fileName", "someFileName"]]
But it was called with:
  ["mimeType", "someMimeType"], ["filename", "someFileName"]

What is the right way to use toHaveBeenCalledWith?

11 Answers 11

279

I was able mock multiple calls and check the arguments this way:

expect(mockFn.mock.calls).toEqual([
  [arg1, arg2, ...], // First call
  [arg1, arg2, ...]  // Second call
]);

where mockFn is your mocked function name.

5
  • 6
    this should be the "best answer" Commented Feb 27, 2018 at 15:52
  • 1
    Also, note that if you don't know exactly what the parameters should be, you can use things like expect.objectContaining.
    – Hew Wolff
    Commented Nov 30, 2020 at 19:59
  • 5
    I had to use this syntax to make it work: expect(mockFn.mock.calls.allArgs()).toEqual([ ...]);. For reference, I am using Jasmine version 4. Commented Jul 13, 2022 at 8:12
  • 1
    I am aware of toHaveBeenNthCalledWith, as mentioned in other answers, but I feel that this solution is terser and easier to read.
    – Holf
    Commented Jul 22, 2022 at 16:55
  • What if I don't want to test the order of the calls? Just the containing parameters?
    – Bolein95
    Commented Nov 3, 2022 at 16:20
171

Since jest 23.0 there is .toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....) https://jestjs.io/docs/expect#tohavebeennthcalledwithnthcall-arg1-arg2-

Also under the alias: .nthCalledWith(nthCall, arg1, arg2, ...)

If you have a mock function, you can use .toHaveBeenNthCalledWith to test what arguments it was nth called with. For example, let's say you have a drinkEach(drink, Array<flavor>) function that applies f to a bunch of 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:

test('drinkEach drinks each drink', () => {
  const drink = jest.fn();
  drinkEach(drink, ['lemon', 'octopus']);
  expect(drink).toHaveBeenNthCalledWith(1, 'lemon');
  expect(drink).toHaveBeenNthCalledWith(2, 'octopus');
});

Note: the nth argument must be positive integer starting from 1.

2
  • 3
    While this link may assist in your answer to the question, you can improve this answer by taking vital parts of the link and putting it into your answer, this makes sure your answer is still an answer if the link gets changed or removed :) Commented May 30, 2018 at 9:42
  • Even if not provided an example in the answer, this point is really valid! up to yoU!
    – quirimmo
    Commented Aug 30, 2018 at 14:02
38

You can also test toHaveBeenCalledWith and test multiple times for each expected parameter combination.

One example is Google Analytics plugin api uses the same function call with different parameter combinations.

function requireGoogleAnalyticsPlugins() {
  ...
  ga('create', 'UA-XXXXX-Y', 'auto');
  ga('require', 'localHitSender', {path: '/log', debug: true});
  ga('send', 'pageview');
}

To test this the below example tests that ga has been called three times with the various parameter combinations.

describe("requireGoogleAnalyticsPlugins", () => {
  it("requires plugins", () => {
    requireGoogleAnalyticsPlugins();
    expect(GoogleAnalytics.ga.toHaveBeenCalledTimes(3);
    expect(GoogleAnalytics.ga).toHaveBeenCalledWith('create', 'UA-XXXXX-Y', 'auto');
    expect(GoogleAnalytics.ga).toHaveBeenCalledWith('require', 'localHitSender', {path: '/log', debug: true});
    expect(GoogleAnalytics.ga).toHaveBeenCalledWith('send', 'pageview');
  });
});

In OP case you could test this with

expect(formData.append).toHaveBeenCalledWith('mimeType', 'someMimeType');
expect(formData.append).toHaveBeenCalledWith('fileName', 'someFileName');
25

The signature is .toHaveBeenCalledWith(arg1, arg2, ...), where arg1, arg2, ... means in a single call (see).

If you want to test multiple calls, just expect it multiple times.

Unfortunately, I have not yet found a method to test the order of multiple calls.

2
  • 1
    I believe I've addressed how to use a single expect (see my answer below).
    – Andi
    Commented Jan 3, 2018 at 13:59
  • 1
    This method does not work/supported - unfortunately!
    – rubmz
    Commented Jul 29, 2018 at 12:25
12

You can also create an array of the expected arguments per call and loop over it:

const expectedArgs = ['a', 'b', 'c', 'd']
expectedArgs.forEach((arg, index) => 
    expect(myFunc).toHaveBeenNthCalledWith(index + 1, arg))

This solution considers the order of the calls. If you do not care about the order, you can use toHaveBeenCalledWith without the index instead.

2
  • This checks the order of the calls, not just that they have all happened (which may or may not be desired). But toHaveBeenNthCalledWith wants 1 if it is checking the first call, not 0, so the index is off by one. Commented Jun 24, 2020 at 9:09
  • 1
    @JacobRaihle Thanks for pointing that out. :) I have updated the answer according to your comment.
    – Kim Kern
    Commented Jun 24, 2020 at 9:17
9

Another solution based on Andi's one. Select the call you want and check the value of the arguments. In this example the first call is selected:

expect(mockFn.mock.calls[0][0]).toEqual('first argument');
expect(mockFn.mock.calls[0][1]).toEqual('second argument');

I recommend to check this Jest cheatsheet:

https://devhints.io/jest

3

To reset the count of the mocks, you can call jest.clearAllMocks.

This is most useful in a beforeEach between tests.

beforeEach(() => jest.clearAllMocks());
2

As a lot of sync calling happened, I encountered some calls the order would be different. Thanks for this post, so I got a way to validate this and ignore the orders.

expect(mockFn.mock.calls).toMatchObject(expect.arrayContaining(
  [
      objectContaining(oneOfYourObject)
  ],
  [
      objectContaining(oneOfYourObject)
  ],
  [
      objectContaining(oneOfYourObject)
  ]
));
1

There is another solution for Jasmine:

expect($.fn.get.calls.allArgs()).toEqual([[], [1, 3], ['yo', 'ho']])

More details here: https://github.com/jasmine/jasmine/issues/228

0

This worked for me as well...initial page load does a default search...user interaction and click search does another search...needed to verify the search process augmented the search values correctly...

let model = {
        addressLine1: null,
        addressLine2: null,
        city: null,
        country: "US"};
let caModel = { ...model, country: "CA" };
const searchSpy = props.patientActions.searchPatient;
expect(searchSpy.mock.calls).toEqual([[{ ...model }], [{ ...caModel }]]);
0

You can use .calls on your spy that returns a Calls interface with amongst others the following method:

/** will return the arguments passed to call number index **/
argsFor(index: number): any[];

So then you can do the following:

const formData = { append: jest.fn() };
const file = { name: 'someFileName', type: 'someMimeType' };
eventHandlers.onUploadStart(file, null, formData);

expect(formData.append).toHaveBeenCalledTimes(2);
expect(formData.append.argsFor(0)).toEqual(
  ['fileName', 'someFileName']
);
expect(formData.append.argsFor(1)).toEqual(
  ['mimeType', 'someMimeType'],
);

Not the answer you're looking for? Browse other questions tagged or ask your own question.