How to check multiple arguments on multiple calls for jest spies?
JavascriptTypescriptUnit TestingJasmineJestjsJavascript Problem Overview
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
?
Javascript Solutions
Solution 1 - Javascript
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.
Solution 2 - Javascript
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.
Solution 3 - Javascript
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.
Solution 4 - Javascript
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');
Solution 5 - Javascript
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.
Solution 6 - Javascript
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:
Solution 7 - Javascript
To reset the count of the mocks, you can call jest.clearAllMocks
.
This is most useful in a beforeEach
between tests.
beforeEach(() => jest.clearAllMocks());
Solution 8 - Javascript
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 }]]);
Solution 9 - Javascript
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'],
);