I am mocking a module using Jest because it contains code that shouldn't run in the test. However I can see from the output that code in the module is being run.
// foo.js
console.log('Hello')
// test.js
jest.mock('./foo')
const foo = require('./foo')
test.todo('write some tests')
Console output
PASS test.js
✎ todo 1 test
console.log foo.js:1
Hello
What's up with that?
I am mocking a module using Jest because it contains code that shouldn't run in the test. However I can see from the output that code in the module is being run.
// foo.js
console.log('Hello')
// test.js
jest.mock('./foo')
const foo = require('./foo')
test.todo('write some tests')
Console output
PASS test.js
✎ todo 1 test
console.log foo.js:1
Hello
What's up with that?
Share Improve this question asked Apr 11, 2019 at 12:14 TamlynTamlyn 23.6k13 gold badges119 silver badges137 bronze badges2 Answers
Reset to default 20This has tripped me up a couple of times.
If you don't provide a mock implementation to jest.mock
it will return an object which mirrors the exports of the mocked module but with every function replaced with a mock jest.fn()
. This is pretty neat as it is often what you want. But in order to determine the exports of the module, it must first require
it. This is what is causing the console.log
to be run.
Two possible solutions:
- Don't run code in the top level of the module: instead export a function which runs the code.
- Provide your own mock implementation so it doesn't need to introspect the module
jest.mock('./foo', () => {})
Jest still executes automocked modules because that's how automock works.
Instead of static code analysis, it attempts to execute module script and analyze result object properties.
The result object properties (usually, functions) are used as a 'blueprint' to create mock module with same properties but mocked values.
Have a look on Jest's mock generation code:
_generateMock(from, moduleName) {
const modulePath =
this._resolver.resolveStubModuleName(from, moduleName) ||
this._resolveModule(from, moduleName, {
conditions: this.cjsConditions
});
if (!this._mockMetaDataCache.has(modulePath)) {
// This allows us to handle circular dependencies while generating an
// automock
this._mockMetaDataCache.set(
modulePath,
this._moduleMocker.getMetadata({}) || {}
); // In order to avoid it being possible for automocking to potentially
// cause side-effects within the module environment, we need to execute
// the module in isolation. This could cause issues if the module being
// mocked has calls into side-effectful APIs on another module.
const origMockRegistry = this._mockRegistry;
const origModuleRegistry = this._moduleRegistry;
this._mockRegistry = new Map();
this._moduleRegistry = new Map();
const moduleExports = this.requireModule(from, moduleName); // Restore the "real" module/mock registries
this._mockRegistry = origMockRegistry;
this._moduleRegistry = origModuleRegistry;
const mockMetadata = this._moduleMocker.getMetadata(moduleExports);
if (mockMetadata == null) {
throw new Error(
`Failed to get mock metadata: ${modulePath}\n\n` +
'See: https://jestjs.io/docs/manual-mocks#content'
);
}
this._mockMetaDataCache.set(modulePath, mockMetadata);
}
In this code, the 'real' module code will be executed in line
const moduleExports = this.requireModule(from, moduleName);
And as mentioned in Tamlyn's answer, it can be avoided by avoiding automock, via manual mock implementation.