Mocking different values for the same module using Jest
Module mocks are a powerful tool to write unit tests with Jest. They allow you to isolate the code under test from its dependencies, leading to focused, less brittle tests. But, as many other powerful tools, module mocks can be tricky at times.
In this post we’ll explore how to mock different values for the same module in different tests.
Say you have a greetings
module exporting a hello
function which depends on another module to know the current language of the application. Something like this:
Mock me once, shame on you. Mock me twice… shame on you again?
Writing a unit test for hello
involves mocking the lang
dependency in order to control the current language:
You can use jest.mock
(line 4) to mock the lang
dependency. In the example above, the mock module has a current
field which is set to a mock function. You want to test both branches of hello,
so you use mockReturnValueOnce
to make the mock function return "GL"
in the first invocation, and"EN"
in the second one.
You run jest
, both tests pass, mission accomplished. You are a happy developer.
But wait. Try to focus the second test using it.only
. Aw fish! Now the test fails:
Expected value to equal:
"Hi!"
Received:
"Ola!"
Well, it seems that the mock module setup is too brittle: you expect the mock function to be called in the same order you are defining it. That couples your test execution order to the mock setup, and that is… well, not good :)
Keep your friends close, and your mocks closer
There is a better way to setup a test like this one:
The key difference lies in lines 3, 13 and 20. You import the mocked module (line 3) to gain access to the mock function. Then, you call mockImplementation
(lines 13 and 20) inside the test body to setup the right return value. Now you can use it.only
whenever you want!
Mocking values, not functions
Suppose greetings
changes: now it must use a different module to get the current language value. The new module is called appEnv
and it exports the current language as a value. Now greetings
looks like this:
You try and change the test accordingly:
You run jest
again and… it fails! You get an error message:
greetings.test.js: "currentLanguage" is read-only
The problem is that you can’t assign a value to something you have imported. In the previous examples, you imported the mock function current
, and you used mockImplementation
to change its return value, but the imported value stayed the same¹. Now you can’t do that.
Don’t import, require
What you need is a way to use a different mock for each test. If you try something like this, you’ll still see a failing test:
In the previous code snippet, hello
is imported before its dependency is mocked, so the tests are executed using the actual implementation of appEnv
. It’s time to ditch all that ES6 fancy stuff. Just use a good ol’ require
once you are done setting up the module mock:
Run the tests now… Still red, right? Well, you need to tell Jest to clear the module registry before each test, so each time you call require
you get a fresh version of the required module.
This is the final, working code:
Summing up
We’ve seen how to mock a module to export different values for different tests. When the export is a function, you can mock it with jest.fn()
and change its implementation for each test. When the export is a value, you need to go back to the basics and use require
(and jest.resetModules
) to ensure the order of execution doesn’t interfere with your mock setup².
¹ Well, technically it is the binding (not the value) what stays the same.
² You might want to take a look at jest.doMock
if you want to change the mock value between two different assertions of the same test.