-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
spyOn docs misleading? #4828
Comments
Not sure what we should recommend here. Personally I always add However, if you fix your test all is good (the next test is failing because of collateral damage*), so maybe adding a comment about the gotcha would be enough? Or adding *no idea if that sentence works in English, best translation I can come up with from Norwegian :P |
We could perhaps just always restore. If you want to mock every single invocation, you could add it in a |
I always avoid spies and mocks as long as I can because I think they are complicated by nature :) I wrote some tests for a browser extension a few years ago, where I had to modify and assert lots of global stuff. What I did there was passing a test("getPrice 1", (teardown) => {
const spy = jest.spyOn(product, "getPrice").mockImplementation(() => 0);
teardown(() => {
spy.mockRestore();
});
expect(product.getPrice()).toBe(42);
}); But maybe I'm missing how you're supposed to mock things. Maybe I shouldn't create one |
I think I ran into a side effect of this behavior. First, to ensure restoration of mocks, I used describe('my tests', () => {
let spy
afterEach(() => {
if(spy) {
spy.mockReset()
spy.mockRestore()
}
})
test("getPrice 1", () => {
spy = jest.spyOn(product, "getPrice").mockImplementation(() => 0)
expect(product.getPrice()).toBe(42)
})
}) This works great until I added another test that spied as well: describe('my tests', () => {
let spy
afterEach(() => {
if(spy) {
spy.mockReset()
spy.mockRestore()
}
})
test("getPrice 1", () => {
spy = jest.spyOn(product, "getPrice").mockImplementation(() => 0)
expect(product.getPrice()).toBe(42)
})
test("getPrice 2", () => {
spy = jest.spyOn(product, "getPrice").mockImplementation(() => 42)
expect(product.getPrice()).toBe(42)
})
}) However test 2 fails and I suspect its got something to do with the fact that So if we can't rely on |
@lydell It's not that mocks are complicated, you are being mislead by the const product = {
getPrice: () => 1337,
}; It's easy to think that the object is a const but the Another and better alternative is to generate a new product object for each test. @mrchief Your example works for me, I can't reproduce the error. Try using |
@borela I've refactored my tests since then but I'll try and see if I can find a reproducible sample for you. |
@borela I know how |
@lydell They modify the function/method. If possible, try to create a new test object for each test, it'll prevent many gotchas: let product
beforeEach(() => {
product = {
getPrice: () => 1337,
};
}) |
Ah, I see! I guess the documentation example could be rewritten to: const Video = require('./video');
test('plays video', () => {
const video = new Video();
const spy = jest.spyOn(video, 'play');
const isPlaying = video.play();
expect(spy).toHaveBeenCalled();
expect(isPlaying).toBe(true);
spy.mockReset();
spy.mockRestore();
}); But really, when creating a new object in every time there's not even a need for resetting stuff? const Video = require('./video');
test('plays video', () => {
const video = new Video();
const spy = jest.spyOn(video, 'play');
const isPlaying = video.play();
expect(spy).toHaveBeenCalled();
expect(isPlaying).toBe(true);
}); I guess we might still want an example where |
I agree it needs better examples. If you are sure your tests don't modify the test object you are safe to share it, but in most cases creating a new one is extremely fast and the benefit is total isolation between tests which as you mentioned, you wouldn't need to reset anything. Only in cases where setting up the test object is costly(test databases, etc... integration tests) that I would share the test object and consider using either
Unless I had to set up thousands of mocks which could make my tests slow by setting the mocks up on |
What's the point in calling both I've done some digging through the issues and PRs. Seems like the bundle of So far my understanding is this:
In other words, And it also seems like So if |
@geoffreyyip yeah, you probably wouldn't want to use both what about updating the example to: const video = require('./video');
const spy = jest.spyOn(video, 'play');
test('plays video', () => {
const isPlaying = video.play();
expect(spy).toHaveBeenCalled();
expect(isPlaying).toBe(true);
}); This gets you going without any gotchas - it just assumes that you'll use the mock for the entire suite |
@rickhanlonii I need to reset/restore/clear the mock between tests. The file I'm testing has recursion. Functions depend on each other. Imagine I had But then I finish testing It feels like I'd use |
@geoffreyyip you probably want |
@rickhanlonii With all due respect, I have to disagree. This issue is about potentially misleading documentation. Docs are part of the repo and deserve attention when there is ambiguity.
Listing both
|
Yes I agree, sorry for the confusion. Your previous comment seemed to be a question about your specific use case and not the documentation. The docs should not say to use both, and I think we explain the difference between the three pretty well here |
Hmm... now that you point it out, my question is about a specific use case. And there is a judgment call on whether the example should be updated to be I suppose I'm expecting Jest to teach me about spies and mocks, b/c Sinon is more configuration heavy on this front. Maybe that's expecting too much, haha. I have some more thoughts on Thanks! |
No sweat! I think that for this particular location in the docs, mentioning any of the mockClear/Reset/Restore functions just begs more questions - that behavior can be outlined in a spyOn guide where there's room to explain how and when to use each |
being faked by spyOn's doc on mockReset/ mockRestore/ mockClear for an hour to figure our mockClear is what i am looking for. |
@darylszehk01 any suggestions on how we can improve the docs now that you understand the difference? |
This issue is stale because it has been open for 1 year with no activity. Remove stale label or comment or this will be closed in 30 days. |
Here’s a test I wrote recently: describe("Init change cmd", () => {
const originalConsoleLog = console.log;
afterEach(() => {
console.log = originalConsoleLog;
});
test("Init change cmd", async () => {
const mockConsoleLog = jest.fn();
console.log = mockConsoleLog;
doStuff();
expect(mockConsoleLog.mock.calls).toMatchInlineSnapshot();
});
}); I’ve noticed this pattern in my tests lately: Sometimes I wrap a |
Yeah, having a clean way of adding setup and teardown to a single test would be great. But for this issue about the docs - would adding an admonition about this gotcha be enough? |
If there is no better solution currently, then yes. |
@lydell in your specific example from OP, describe("product", () => {
afterEach(() => {
jest.restoreAllMocks();
});
}); Could also set that in config. I.e., just ensure that the cleanup always runs instead of within the test function itself. Ideas on how to include that info in the docs? |
What about updating the example in the docs to include an appropriate afterEach(() => {
jest.restoreAllMocks();
});
|
Yeah, that sounds like a good idea to me. |
Hi @SimenB, I can work on it, what do you think? |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
The jest.spyOn example in the docs looks like this:
My questions is: Is that a good example? If an expectation fails, later tests that also use
video
will unexpectedly get a mocked.play()
method.Here's a concrete example:
Above, both tests are failing and the user has to find out which test to "fix" first.
Lots of people, including me, copy stuff from docs all the time. So it is a good thing if the examples are really good :)
Unfortunately, I don't know what the best practice is here, otherwise I could have made a PR.
The text was updated successfully, but these errors were encountered: