Do you want to request a feature or report a bug?
Feature
What is the expected behavior?
I'd like to assert that one mock function is called before another similar to Sinon's calledbefore
spy.
Is this a feature that's been considered/is being considered?
What would you like an API to look like for this in Jest?
I'd guess
const spy1 = jest.fn();
const spy2 = jest.fn();
spy1();
spy2();
expect(spy1).toHaveBeenCalledBefore(spy2);
How does one handle multiple invocations?
@SimenB's API looks good to me.
Wouldn't multiple invocations be a separate thing? What's the use case for this?
const spy1 = jest.fn();
const spy2 = jest.fn();
spy1();
spy2();
spy1();
expect(spy1).toHaveBeenCalledBefore(spy2);
Might be a non-issue, but spy1
is called technically both before _and_ after spy2
Apologies for the bump! Just thought it might be worth registering the fact that I ended up on this issue as a result of a Google search cos I was half-wondering if there was a built-in way to accomplish this in Jest 馃槃
You can add a custom matcher for this using expect.extend
.
If you _do_ end up creating the matcher, please publish it and post about it on here. We can link to it in the docs
@SimenB can this really been done with extend? any call to a jest.fn()
method would need to leave an array of timestamps or a call counter somewhere you can read from within the expect and I can't see anything that would qualify?
jestFn.mock.calls
is an array that is pushed into on each calls, so you can assert order in the array.
https://facebook.github.io/jest/docs/en/mock-functions.html#mock-property
jestFn.mock.calls is an array that is pushed into on each calls
That's per mock function, not overall, right? So I am unsure on how that would help in comparing whether one (jest.fn())()
call was done before another.
Ah, good point. Brainfart. Not as easy to fix on our side either, then. Adding an extra field to the mockstate feels overkill.
Code is here, if anyone wants to tinker with it: https://github.com/facebook/jest/blob/b8e2fe384c17f3fb2abe8b0bc678d2ddbb11af1b/packages/jest-mock/src/index.js#L314-L315
(I still don't think we should add the matcher to jest, but we should make it _possible_ to add in userland)
Hey @gavboulton fancy seeing you here 馃槅 馃憢
@SimenB I've added the extra field for this in #4866 if you get a chance to take a look :smile:
That PR has been merged. https://github.com/jest-community/jest-extended/issues/98 can be tracked for the matcher
It appears that using Date.now()
isn't precise enough to use toHaveBeenCalledBefore(mock)
from jest-extended (jest-community/jest-extended#98).
I've been playing around with using process.hrtime() as the timestamp value instead. And I've been getting good results. I'd be happy to submit a PR if no one has any objections to this.
Yeah, we should probably use a global counter instead of a timestamp to match jasmine (https://github.com/jasmine/jasmine/blob/847a959b1380cd30b9133d85270ce9aebbcb690d/src/core/Spy.js#L39).
We're getting closer to a new major of Jest, so a PR removing timestamps
from the mock state and adding a counter instead makes sense to me.
Would this new counter be instantiated outside of ModuleMockerClass
? I'm asking because I'm not sure of a way to assert call order between different mocks if the counter is instantiated inside of that class.
Edit: Ignore this comment. Not thinking straight. I may submit a PR for this new counter in a bit
This is how I worked around it:
function setup() {
const callOrder = [];
const init = jest.fn().mockImplementation(() => callOrder.push('init'));
const flush = jest.fn().mockImplementation(() => callOrder.push('flush'));
// ... other stuff
return { callOrder /* other stuff */ };
}
it('implements taboola', () => {
const { callOrder } = setup();
// ... other stuff
expect(callOrder).toEqual(['init', 'flush']);
});
I doubt the upper function is testing anything 馃槄
My solution to this (where I want to make sure logout is called before login):
const logoutSpy = jest.spyOn(client, 'logout');
const loginSpy = jest.spyOn(client, 'login');
// Run actual function to test
await client.refreshToken();
const logoutOrder = logoutSpy.mock.invocationCallOrder[0];
const loginOrder = loginSpy.mock.invocationCallOrder[0];
expect(logoutOrder).toBeLessThan(loginOrder);
Here is a little example:
// grabSnake.js
export default function grabSnake (snake, headFirst, force) {
if (headFirst) {
snake.head(force)
snake.tail(force)
} else {
snake.tail(force)
snake.head(force)
}
}
// ./__tests__/grabSnake.js
import grabSnake from "./grabSnake"
const spy = jest.fn()
const snake = {
head (...args) {
spy(`snake.head()`, args)
},
tail (...args) {
spy(`snake.tail()`, args)
},
}
describe(`./grabSnake`, () => {
afterEach(() => spy.mockClear())
test.each([[true], [false]])(`should grab in the right order (headFirst %j)`, headFirst => {
grabSnake(snake, headFirst, 11)
expect(spy.mock.calls).toMatchSnapshot()
})
})
For future readers: https://github.com/jest-community/jest-extended#tohavebeencalledbefore
Most helpful comment
This is how I worked around it: