jest: [Bug]: jest.restoreAllMocks behavior changed/broken
Version
29.4.3
Steps to reproduce
This worked prior to version 29.4.3
mockedItme = jest.fn().mockImplementation(() => {return 'hi'});
beforeEach(() => {
jest.restoreAllMocks();
jest.resetModules();
jest.clearAllMocks();
});
describe("mockTest", () => {
test("test1", () => {
expect(mockedItme()).toEqual('hi');
});
test("test2", () => {
expect(mockedItme()).toEqual('hi');
});
})
Expected behavior
Previous behavior test passed on all platforms.
Actual behavior
Now restoreAllMocks actually deletes all mocks
Additional context
No response
Environment
Mac M1, Windows 10, Linux docker node14-alpine
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 8
- Comments: 35 (15 by maintainers)
Commits related to this issue
- Account for Jest 29.4.3 mock restore changes https://github.com/facebook/jest/issues/13925 — committed to CreativeTechGuy/ReactTemplate by CreativeTechGuy a year ago
@mrazauskas is there any way we can help those who will be struggling with this problem after us? Maybe an amendment to the CHANGELOG which links to migration guide and calls it out as an unexpectedly breaking change. Or a pinned issue which is a summary of the problem and a few options on what someone can do going forward, etc. I checked the npm download stats and there’s a significant number of users on versions prior to this change so even though it’s already been out for a month, there’s definitely millions of more people who will run into this issue.
@mrazauskas
The fact that it is mocked.
Let me explain it another way hopefully this will be clearer.
I see there to be two different kinds of mocks/spies. There’s test-specific ones and global ones.
matchMedia
mock. They should be mocked all the time for every test in every file. You don’t ever want these to be reset, but you do want them to be cleared so the count of calls etc doesn’t carry over between test cases.(A simple way to think of this is at the start of every
describe
/test
block, it creates a snapshot of what mocks exist and restores to that snapshot when that block ends. Any additional mocks that were created get reverted, any mocks that were there in the snapshot persist.)I know we could manually restore and clear each one individually, but this is very error prone and the whole reason the
restoreMocks: true
andclearMocks: true
configs exist.I had thought the previous behavior (maybe this was the bugged behavior) was closer to what I described above. Not sure if it wasn’t working exactly as I had expected, but it sure seemed like it. Now though after this has been “fixed”, I’m not sure how to accomplish what I described in a generic way. I either need to re-mock every global mock before every test, or make sure to restore every spy/mock that was created within a test before it ends.
So I’m guessing there’s no way to restore mocks that were created during a test before the next test? So if I spied on a method, changed the implementation to trigger some special behavior for that test case, now I need to make sure that I manually restore it before that test case ends? And somehow also restore it if the test case fails? That’s a big footgun. 😞
I totally understand that this may have been long-standing bugged behavior and I’m glad it’s fixed, but that might mean we need to add a new config option to accomplish what people were intending to do prior which is now very difficult to achieve.
Wow! I really appreciate you trying to explain this. It begs the question, with how complex this behavior was before (which existed for a long time) and many people depended on that functionality thinking it was intentional, why was this changed in a patch with no info? This is clearly very confusing behavior. It definitely is more clear now since it’s consistent, but also definitely breaking since it’s notably different than what it was doing for a long time.
My takeaway from all of this is that if you were using
restoreMocks: true
before and it was working for you, the change to make is to keep that setting, but now ensure that any mocks which are created outside of a test case are now inside of abeforeEach
block so they are remocked before every test after the previous ones are cleared. This will ensure that each test runs with a clean slate like I believe many people were hoping for.Example:
Before:
After:
If this is the case, which makes sense by reading the documentation, I think restoreAllMocks has been broken for a very long time, and a lot of our developers are using it incorrectly in their tests.
Maybe a breaking fix like this should be saved for the v30 release?
By setting the following dependencies my test will pass:
But the test @mrazauskas posted fails.
I’m not sure why the jest team uses the “^” in the version for the transitive jest dependenciest? It Makes it very hard to downgrade to a previous version.
If this leads to making more of our tests better, I’m all for it. We’ll be making the changes in our end even if it means in the thousands (we’ll find a way to automate them).
Because previously the restoreMocks almost did nothing, but now the
restoreMocks
seems equals to thereset to jest.fn() for those Non-SpyOn mocks + restore to the original implementation for those SpyOn mocks
, so it cause a lot of test failed, since a lot of mock are defined on the first few lines of the file or thesetupTest.js file
, like some package mocks; Those have been restored;so it just need remove
restoreMocks: true
if there is no special need to use it in the projectdoing this fixed the majority of the test failures for us. The few that remained were all legit test issues that I’m surprised worked in earlier versions.
@ghiscoding Wait… I missed
mockImplementationOnce
. Hm… Those should work. Sorry. I will take a better look tomorrow. Thanks for pointing to this.@ghiscoding Just checked your case. All you need is
jest.clearAllMocks()
.The test file has many assertions around
exec
, like this one:expect(exec).toHaveBeenCalled()
. To make this work you have to clear count of calls and this is whatclearAllMocks()
will do.jest.restoreAllMocks()
does clear the count as well, but it also restores this mock (since 29.4.3 fixed a bug): https://github.com/lerna-lite/lerna-lite/blob/aa35a81736930755d71b3948cdf777da14defa86/packages/version/src/__tests__/git-push.spec.ts#L16Currently only first tests will pass and all the others will have the mock restored (with implementation removed). This is what you see running this test file.
I found from older documentation it says “restoreAllMocks()” only affects spies, but now affects all:
Yes, we fixed our test, after a day of trying to figure out what went wrong. Please don’t backport this fix to v27 or we will have a very big problem on our hands with all our old apps that just stop building…
The thing that makes this even worse is there is no way to “pin” a. specific version of jest because of the pointless use of carets “^” throughout all the nested dependencies: https://github.com/facebook/jest/issues/3391
Which makes trying to test against 29.4.2 almost impossible when 29.4.3 is released.
I agree, but it did not look breaking: #13867. In the other hand, I think swapping
jest.restoreAllMocks()
withjest.clearAllMocks()
orrestoreMocks: true
withclearMocks: true
should be enough. I don’t think users really wanted to restore their mocks before each test. If they really wanted that, an issue should be raised long time ago.