Reputation: 2441
I have a Node.js app where index.js
has different exports for Unix-like and Windows platforms.
import os from "os";
function throwNotSupportedError() {
throw new Error("Platform not supported.");
}
console.log(os.platform());
switch (os.platform()) {
case "darwin":
case "linux":
module.exports = {
foo: require("./unix/foo"),
bar: require("./unix/bar")
};
break;
case "win32":
module.exports = {
foo: require("./win32/foo"),
bar: require("./win32/bar")
};
break;
default:
throwNotSupportedError();
}
And I am trying to cover this file with unit tests which looks like this:
import os from "os";
jest.mock("os");
describe("Linux platform", () => {
test("has `foo` and `bar` methods on Linux platform", () => {
os.platform.mockImplementation(() => "linux");
const app = require("../src");
expect(app.foo).toBeTruthy();
expect(app.bar).toBeTruthy();
});
});
describe("Windows platform", () => {
test("has `foo` and `bar` methods on Windows platform", () => {
os.platform.mockImplementation(() => "win32");
const app = require("../src");
expect(app.foo).toBeTruthy();
expect(app.bar).toBeTruthy();
});
});
The thing is that os.platform.mockImplementation(() => "win32");
works but console.log(os.platform());
still shows linux
even if I import the app in every test case const app = require("../src");
.
Where is my mistake and how to solve it?
Upvotes: 2
Views: 3400
Reputation: 438
Khang's answer about jest.resetModules()
is pointing to the correct direction. I would like to add that when you reset modules, references to any past imports will be "ignored" (a new instance is created after reset). In other words, the import os from "os";
at the top of your test will no longer be used after the module reset.
Solution
In addition to jest.resetModules()
you need to re-import (or in this case re-require) the os
module inside the test you are about to execute. By doing so, os.platform.mockImplementation(() => "win32");
will be applied to the latest instance of the module's mock. Both your tests need to be structured this way;
test("has `foo` and `bar` methods on Windows platform", () => {
const os = require('os');
os.platform.mockImplementation(() => "win32");
const app = require("./os-test");
expect(app.foo).toBeTruthy();
expect(app.bar).toBeTruthy();
});
You might want to use beforeEach
instead of afterEach
, to ensure that the os
module is clean before the test. Jest should isolate each test file, but better safe than sorry? Finally, you would want to have the beforeEach
to run before all tests, not just inside the "Windows platform" describe
. To do that you can either move it to the root of the file or wrap the two describe
in an additional describe
, e.g.
jest.mock("os");
describe('Platform specific module', () => {
beforeEach(() => {
jest.resetModules();
});
describe("Linux platform", () => {
test("has `foo` and `bar` methods on Linux platform", () => {
const os = require('os');
os.platform.mockImplementation(() => "linux");
...
I hope this helps! Mocking tends to be tricky, not just in Jest.
References
jest.resetModules()
official documentationUpvotes: 3
Reputation: 7748
You would need to use jest.resetModules()
after each test to clear the module cache:
describe("Windows platform", () => {
afterEach(() => {
jest.resetModules();
})
//...
})
Upvotes: 0
Reputation: 1287
You are mocking the os
module in the scope of your test, but your actual code uses the module in its own scope. jest.mock
only takes the exported methods and replaces them with jest.fn
, so, in theory, your code needs to export os
, then your test code should just require your code once at the top of the file. Your test code should not import os
directly.
This is just untested theory, by the way, from reading a tutorial on Jest mocks.
Upvotes: 0