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.
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:
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
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:
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:
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
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.