What's the best way to correctly mock the following example?
The problem is that after import time, foo
keeps the reference to the original unmocked bar
.
module.js:
export function bar () {
return 'bar';
}
export function foo () {
return `I am foo. bar is ${bar()}`;
}
module.test.js:
import * as module from '../src/module';
describe('module', () => {
let barSpy;
beforeEach(() => {
barSpy = jest.spyOn(
module,
'bar'
).mockImplementation(jest.fn());
});
afterEach(() => {
barSpy.mockRestore();
});
it('foo', () => {
console.log(jest.isMockFunction(module.bar)); // outputs true
module.bar.mockReturnValue('fake bar');
console.log(module.bar()); // outputs 'fake bar';
expect(module.foo()).toEqual('I am foo. bar is fake bar');
/**
* does not work! we get the following:
*
* Expected value to equal:
* "I am foo. bar is fake bar"
* Received:
* "I am foo. bar is bar"
*/
});
});
Thanks!
EDIT: I could change:
export function foo () {
return `I am foo. bar is ${bar()}`;
}
to
export function foo () {
return `I am foo. bar is ${exports.bar()}`;
}
but this is p. ugly in my opinion to do everywhere :/
I had this same problem and due to the project's linting standards, defining a class or rewriting references in the
exports
were not code review approvable options even if not prevented by the linting definitions. What I stumbled on as a viable option is to use the babel-rewire-plugin which is much cleaner, at least in appearance. While I found this used in another project I had access to, I noticed it was already in an answer in a similar question which I have linked here. This is a snippet adjusted for this question (and without using spies) provided from the linked answer for reference (I also added semicolons in addition to removing spies because I'm not a heathen):https://stackoverflow.com/a/45645229/6867420
fwiw, the solution I settled on was to use dependency injection, by setting a default argument.
So I would change
to
This is not a breaking change to the API of my component, and I can easily override bar in my test by doing the following
This has the benefit of leading to slightly nicer test code too :)
An alternative solution can be importing the module into its own code file and using the imported instance of all of the exported entities. Like this:
Now mocking
bar
is really easy, becausefoo
is also using the exported instance ofbar
:Importing the module into its own code looks strange, but due to the ES6's support for cyclic imports, it works really smoothly.
The problem seems to be related to how you expect the scope of bar to be resolved.
On one hand, in
module.js
you export two functions (instead of an object holding these two functions). Because of the way modules are exported the reference to the container of the exported things isexports
like you mentioned it.On the other hand, you handle your export (that you aliased
module
) like an object holding these functions and trying to replace one of its function (the function bar).If you look closely at your foo implementation you are actually holding a fixed reference to the bar function.
When you think you replaced the bar function with a new one you just actually replaced the reference copy in the scope of your module.test.js
To make foo actually use another version of bar you have two possibilities :
In module.js export a class or an instance, holding both the foo and bar method:
Module.js:
Note the use of this keyword in the foo method.
Module.test.js:
Like you said, rewrite the global reference in the global
exports
container. This is not a recommended way to go as you will possibly introduce weird behaviors in other tests if you don't properly reset the exports to its initial state.If you define your exports you can then reference your functions as part of the exports object. Then you can overwrite the functions in your mocks individually. This is due to how the import works as a reference, not a copy.