Last time, I wrote a post on Jest testing CSS Module components. The article mentioned using Jest Mock to allow Jest to skip recognition conversions to CSS and other files. This ability can also be used to isolate factors encountered during testing that are uncontrollable or inconvenient to test. Such as:

  • Third-party API Calls
  • Things like writing files that are not helpful for testing
  • Global interfaces provided by the application environment
  • Delay operation (using timer)

These uncontrollable factors make it impossible to tell whether the target test code is faulty or caused by unexpected changes in these factors. Then the tests we worked so hard to write are meaningless.

At this point, we can isolate these uncontrollable factors using a series of tools built into Jest. Let’s see how different objects mock!

Mock function methods

Let’s start with the jest.fn() method, which is probably the most intuitive of all mock methods.

Assume that the source code and test code are in the same module file (although they are not usually together), like this:

function fun() { return true; }

test("mock fun".() = > {
  expect(fun()).toBeTruthy();
});
Copy the code

If you want to isolate fun() when running tests, you can take advantage of the fact that multiple variables with the same name are encountered in the JS runtime environment, and replace fun() with jest.fn() using the value of the local variable closest to the current code block:

function fun() { return true; }

test("mock fun".() = > {
  const fun = jest.fn();
  expect(fun()).toBeTruthy();  // The test failed
});
Copy the code

After replacing it with jest.fn(), we realized that the original test failed because we used the default empty function mock, which did not provide the output we expected.

If you need a specific output, pass a factory function to jest.fn() telling it what value to return:

const fun = jest.fn(() = > true);
Copy the code

Then the above test can be passed!

The mock ES6 class

Now that we know about jest.fn(), we can use it to mock other things, such as ES6 classes. The ES6 class is really syntactic sugar, with the same Function prototype chain behind it. So we can mock with jest.fn() as well:

const MockClass = jest.fn(() = > {
  return {
    run: () = > true}; }); test("mock class".() = > {
  const ins = new MockClass();
  expect(ins.run()).toBeTruthy();
});
Copy the code

Above we passed in a simple factory function that returns a method to be tested. Of course, this method can also be created with jest.fn() to test its invocation.

Mock asynchronous request

Similarly, asynchronous requests simply invoke the interface methods provided by JS. Fetch, for example, can return a Promise, but it’s a little more cumbersome because we usually convert the data to the returned Response object for further processing. So its factory function will be longer:

global.fetch = jest.fn(() = >
  Promise.resolve({
    json: () = >
      Promise.resolve({
        data: 1,})})); test("mock fetch".async () => {
  expect(await fetch("/").then((res) = > res.json())).toMatchInlineSnapshot(` Object { "data": 1, } `);
});
Copy the code

If the json() method does not mock, it will return an error.

Mock (); mock (); mock ();

function request() {
  return fetch("/").then((res) = > res.json())
}

test("mock fetch".async() = > {const requqest = jest.fn(() = > Promise.resolve(true))
  expect(await request()).toBeTruthy();
});
Copy the code

You can also mock someone else, such as jest-fetch-mock.

There is also an interesting option — mock servers, such as MSW. Since you can also isolate API requests and specify the data to be returned, you can use a mock server. You don’t need to mock the code. Mock the data.

Mock module file

Mock a module or file, except Jest test CSS Module component error? ” In addition to the crude approach of moduleNameMapper mentioned in this article, you can also enable mocks in your test code using the jest.mock() method.

NodeJS fs module for example:

jest.mock("fs");

const fs = require("fs");
const { readFileSync } = require("fs");

test("auto mock module 1".() = > {
  fs.readFileSync("a");
  expect(fs.readFileSync.mock.calls[0] [1]).toBe("a");
});

test("auto mock module 2".() = > {
  readFileSync("b");
  expect(readFileSync.mock.calls[1] [1]).toBe("b");
});
Copy the code

How does this work? NodeJS will load all required modules into the cache, and if the module is found to already exist, it will read directly from the cache. Jest. Mock takes advantage of this by first loading the mock into the cache to impersonate the specified module, and then we read the mock when we require the module.

With ES6 syntax, there is no difference between placing mock statements before and after import because non-dynamically loaded import keyword processing used at the root level of the file takes precedence:

import * as fs from "fs";
import { readFileSync } from "fs";

jest.mock("fs");
Copy the code

Again, this goes against what we said before. In fact, Jest takes this into account by running the ES module with Jest.mock() in preference to import. You can see it here.

With that in mind, we can isolate mocks for more complex situations. Suppose I now have:

Test object fun.js

import * as fs from "fs";
export default function fun() {
  fs.writeFileSync("a.txt"."a");
  return true;
}
Copy the code

And the test code file fun.test.js

import fun from "./fun";

test("test fun()".() = > {
  expect(fun()).toBeTruthy();
});
Copy the code

I want to prevent fs.writefilesync () from actually happening, so I can use jest. Mock:

// fun.test.js
import * as fs from "fs";
import fun from "./fun";

jest.mock("fs");

test("test fun()".() = > {
  expect(fun()).toBeTruthy();
  expect(fs.writeFileSync).toBeCalledWith("a.txt"."a")});Copy the code

Mock () automatically creates a default mock object for all members of the module export, returning undefined. To print other values, we can pass it a factory function like jest.fn() :

jest.mock("fs".() = > ({
  writeFileSync: () = > true
}));
Copy the code

Or create a __mocks__ folder in the module file directory and create a file with the same name (such as __mocks__/fun.js) within that folder. In this file of the same name, we can write our mock:

// __mocks__/fun.js
const fun = jest.fn(() = > true);
export default fun;
Copy the code

Mock (‘./fun’) is then called in the test code, which Jest runs in place of its own default mock behavior.

If you want to mock a third-party module (node_module), or an official module like FS, place the __mocks__ folder at the same directory level as the node_modules folder and create a file named after the module. Like this:

├ ─ ─ __mocks__ │ └ ─ ─ the fs. Js ├ ─ ─ node_modules ├ ─ ─ the SRC │ ├ ─ ─ fun. Js │ └ ─ ─ fun. Test. JsCopy the code

Mock delay operation

So far, it looks like jest.fn() is pretty versatile, but not so much when it comes to time control. Therefore, Jest has two built-in methods to help with testing:

  • jest.useFakeTimers()Will JS built-insetTimeoutSuch interfaces are replaced in the test environment withjest.fn() mock
  • jest.advanceTimersByTime()Perform n milliseconds in advance

Then you can test it like this:

function fun(run) {
  setTimeout(() = > {
    run();
  }, 1000);
}

jest.useFakeTimers();

test("mock timer".async() = > {const run = jest.fn();
  fun(run);
  expect(setTimeout).toBeCalled();   // The test setTimeout was called
  jest.advanceTimersByTime(1000);
  expect(run).toBeCalled();					// Test that my delay callback is called
});
Copy the code

Check out the other test series:

  • Does the front end write unit tests? | creators camp
  • The React application test: configuration Typescript, Jest, ESLint | creator training camp
  • React -testing-library quickly tests react component renderings
  • Jest test CSS Module component error how to do?
  • Use react-testing-library to quickly test react component interactions