Mocking a module API pains
See original GitHub issueFirstly, I think jest is a great test runner and I appreciate all the hard work that has gone in to it. Awesome job š
However, the mock API does irk me. So Iād like to open this issue to see if thereās something that we can do about it. Some documentation changes may help, some different APIs also may be beneficial.
Do you want to request a feature or report a bug? Feature
What is the current API? Here are some of my pain points with the mocking APIs:
Unclear semantics
The difference in semantics between jest.mock
and jest.doMock
are not apparent from the names alone, I have to check the docs to remind myself. Same goes for jest.unmock
and jest.dontMock
. And jest.resetAllMocks
and jest.restoreAllMocks
. And mockFn.mockReset
and mockFn.mockRestore
. I think you get the pictureā¦
jest.mock
hoisting
jest.mock
calls are hoisted to the top of the scope (via babel-jest
). The issues I see here:
-
Hoisting function calls to the top of the current scope is non-standard JS, itās a language extension. Users probably wonāt expect it, which may cause them problems until the new behaviour is learnt. For example:
// original import foo from './foo' jest.mock('./foo') foo() // is this call mocked?
// transpiled output jest.mock('./foo') import foo from './foo' foo() // yes! it is mocked.
-
It feels like the hoisting was added to fit in with the way existing tests were written, rather than attempting to change the way people write tests to fit the mocking problem. I think TestDouble.js has an interesting solution to this problem. An obvious rip off of their API might look like:
let mockedFoo = jest.replace('./foo') let mockedBar = jest.replace('./bar').default // in this case bar is a default export let subject = require('./subject') // subject now depends on mocked foo and bar
The CommonJS (CJS) module format is more flexible for messing around with modules than ES6 modules (ESM). I would suggest keeping both the current API for ESM and introducing the new for CJS tests and in some future version suggest new tests should be written in the CJS style. This way the hoisting behaviour, as well as some of the lesser known caveats of ESM, can be avoided.
Large API surface
The API relating to mocking a module as of v22.4.2 consists of:
jest.mock
/jest.unmock
jest.doMock
/jest.dontMock
jest.setMock
jest.genMockFromModule
import * as foo from './foo'
+jest.spyOn
(See #936)- Manual mocks
require.requireMock
jest.enableAutoMock
/jest.disableAutomock
Are all of these different methods of mocking a module required? Can a simpler, smaller API be found to cover the above cases? I donāt have any immediate answers but would love to see what the community come up with.
I can probably fix most of these by using a 3rd party lib but it would be nice if Jest made some changes so I didnāt have to. I willing to accept that I hold a minority opinion on these APIs and if so, it may not be worth anyones time to work on this. I thought it was worth raising all the same.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:29
- Comments:23 (9 by maintainers)
Top GitHub Comments
I second this.
jest
mock API is convoluted, at best, and generally confusing, IMHO.genMockFromModule
or plain.mock
to mock a module?.mock
that gets automatically hoistedā¦ or is itdoMock
?unmock
?Iām not saying these questions donāt have proper answers. Iām making the point that itās the fact that you need to consider those answers and think about them every time you (or at least I) write a test using
jest
. I end up losing myself in the docs and/or SO more than I feel I should.Hi all, Iāve been using jest for quite a while now. I agree with OP that the runner itself is fantastic. That said, Iāve tried a number of times over the many projects Iāve used jest on to use jest mocks. I always run into confusion and problems. Itās very possible that Iām doing something wrong and have some fundamental misunderstanding, but the current API doesnāt appear to lead to the pit of success.
I think that the problems Iāve run into boil down to a simple fact: jest mocks are not generally safe to use between tests. That is, they leak state between tests by default. As far as I can tell, they leak recordings and implementations. If I
mockReturnValue
in a test then the next test will get that return value too if I donāt manually restore the mock.This plays poorly with some of the other features of jest like manual mocks. Hereās an example of something Iām trying to do right now that led me to this issue.
__mocks__/service.js
__tests__/mytest.js
So, I know Iām doing something āwrongā here. I probably shouldnāt be using manual mocks in these ways. However, the problem doesnāt end with manual mocks. Many of the jest examples in the docs show mocks being created at the module level. This fails too:
Furthermore, the necessity for
jest.mock
to be at the module level makes it more likely thatjest.fn()
's will be created at the module level. Basically, every time Iāve used jest Iāve felt pushed to create mocks at the module level only to find that, like the north, mocks remember.What I think would be fantastic is if:
jest.fn()
's created at the module level (or in manual mocks).I believe, if those were true, then mocks would ājust workā and wouldnāt leak state between tests.
The API mentioned above looks nicer, but, at least for me, this is the much bigger pain point.