简体   繁体   中英

how do you mock a default exported object with jest per test?

I'm currently working on a test where I need to mock a default exported config object to test different configuration possibilities.

I've found a possibility to do this on file basis with fetch.mock but this doesn't give me the possibility to change the mock in each test run.

Is there something like jest.mockImplementation for a mocked Object or how does this work?

I've created a Repo with the example code: here

Unit under test:

import * as React from "react";
import config from "./config";

export default function App() {
  return (
    <div className="App">
      {config.greet ? <h1>Hello user</h1> : <h1>Bye user</h1>}
    </div>
  );
}

config which should be mocked:

const config = { greet: true };

export default config;

What I want to test

import App from "./App";
import React from "react";
import { render } from "@testing-library/react";

/* 
I'm currently only able to mock on per file level but not on test level
jest.mock("./config", () => ({
  __esModule: true,
  default: { greet: false },
}));
*/

describe("App", () => {
  it("renders hello if greeting true", () => {
    jest.mock("./config", () => ({
      __esModule: true,
      default: { greet: true },
    }));
    const { debug } = render(<App />);

    // should render 'Hello user'
    debug();
  });

  it("renders bye if greeting false", () => {
    jest.mock("./config", () => ({
      __esModule: true,
      default: { greet: false },
    }));

    const { debug } = render(<App />);

    // should render 'Bye user'
    debug();
  });
});

EDIT 1

I've found a kind of a workaround. If I do resetModules after each test and do the mock and afterwards do the loading of the Unit under test it is possible to get different values.

import React from "react";
import { render } from "@testing-library/react";

describe("App", () => {
  afterEach(() => {
    jest.resetModules();
  });
  it("renders bye if greeting false", () => {
    jest.doMock("./config", () => ({
      __esModule: true,
      default: { greet: false },
    }));

    const App = require("./App");
    const { debug } = render(<App.default />);
    debug();

    // should render Bye user
  });
  it("renders hello if greeting true", async () => {
    jest.doMock("./config", () => ({
      __esModule: true,
      default: { greet: true },
    }));
    // or when using import syntax
    const App = await import("./App");
    const { debug } = render(<App.default />);

    debug();
    // should render Hello user
  });
});

This works but I don't like the syntax of it. Can you think of a solution where I import App at the beginning of the file and just override the config object in the test? Because when I render more than one component I have to import all config dependant components in each test again. This doesn't feel right.

EDIT 2

I found a way to provide a overwritable mock on a global level. But i'm currently stuck getting the jest-mock function invoked.

import React from "react";
import { render } from "@testing-library/react";
jest.mock("./config", () => jest.fn());
import * as config from "./config";
const mockConfig = (config as unknown) as jest.Mock;
import App from "./App";

describe("App", () => {
  it("renders bye if greeting false", async () => {
    mockConfig.mockImplementation(() => ({
      greet: false,
    }));

    const { debug, container } = render(<App />);
    expect(container.querySelector("h1")?.textContent).toBe("Bye user");
    //config is jest.fn()
    debug();
  });

  it("renders bye if greeting true", async () => {
    mockConfig.mockImplementation(() => ({
      greet: true,
    }));

    const { debug, container } = render(<App />);
    expect(container.querySelector("h1")?.textContent).toBe("Hello user");
    //config is jest.fn()
    debug();
  });
});

EDIT 3

I decided for now that I will provide a 'hook' which I can mock with jest.

const config = {
  greet: true,
};

export function useConfig() {
  return config;
}

export default config;

with this, I'm able to provide a custom mock for useConfig on a global level which I can override in each test call.

import React from "react";
import { render } from "@testing-library/react";
import { mocked } from "ts-jest/utils";
jest.mock("./config", () => ({ __esModule: true, useConfig: jest.fn() }));
import * as config from "./config";
const mockConfig = mocked(config);

import App from "./App";

describe("App", () => {
  it("renders bye if greeting false", async () => {
    mockConfig.useConfig.mockReturnValue({
      greet: false,
    });

    const { debug, container } = render(<App />);
    expect(container.querySelector("h1")?.textContent).toBe("Bye user");
    //config is jest.fn()
    debug();
  });

  it("renders bye if greeting true", async () => {
    mockConfig.useConfig.mockReturnValue({
      greet: true,
    });

    const { debug, container } = render(<App />);
    expect(container.querySelector("h1")?.textContent).toBe("Hello user");
    //config is jest.fn()
    debug();
  });
});

Since this leads to a refactoring of all components that use the config I'm not happy with this as well and am still looking forward for a object-based mocking solution.

You can rather use a mock function for the manual mock of the config file and define its implementation directly before executing the test case like so:

import React from "react";
import { render } from "@testing-library/react";
import App from "./App";

const mockConfig = jest.fn();

jest.mock("./config", () => mockConfig);

describe("App", () => {
  afterEach(() => {
    jest.resetModules();
  });
  it("renders bye if greeting false", () => {
    mockConfig.mockImplementation(() => ({
      __esModule: true,
      default: { greet: false },
    }));

    import("./App").then((module) => {
      const { debug } = render(<module.default />);
      debug();
    });

    // should render Bye user
  });
  it("renders hello if greeting true", () => {
    mockConfig.mockImplementation(() => ({
      __esModule: true,
      default: { greet: true },
    }));
    import("./App").then((module) => {
      const { debug } = render(<module.default />);

      debug();
    });
    // should render Hello user
  });
});

Note: The naming is essential here. You have to prefix your constant with 'mock' (mockConfig). Otherwise you will get an Error saying the variable is out-of-scope.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM