rel1x
rel1x

Reputation: 2441

Reseting node module in Jest

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

Answers (3)

Mattias Nixell
Mattias Nixell

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

Upvotes: 3

kkkkkkk
kkkkkkk

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

HenryTK
HenryTK

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

Related Questions