如何将Jest模拟功能的范围限制为单个测试

时间:2019-11-16 18:13:43

标签: unit-testing jestjs integration-testing react-testing-library

我正在使用Jest + Testing-Library / React编写功能测试。经过几天的摸索,我发现当您使用.mockResolvedValue(...).mockResolvedValueOnce(...)时,模拟的范围不仅限于该测试...

import React from "react";
import { render, waitForElement } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import myApi from '../myApi';
jest.mock('../myApi'); // this will load __mocks__/myApi.js (see below)
import { wait } from '@testing-library/dom';
import App from "../components/App";

afterEach(() => {
  jest.clearAllMocks();
});


describe("App", () => {

    test("first test", async () => {

        myApi.get.mockResolvedValueOnce('FOO');

        // App will call myApi.get() once
        const { container, getByText } = render(<App />);

        await waitForElement(
            () => getByText('FOO')
        );

        expect(myApi.get).toHaveBeenCalledTimes(1);

        // This is going to "leak" into the next test
        myApi.get.mockResolvedValueOnce('BAR');

    });

    test("second test", async () => {

        // This is a decoy! The 'BAR' response in the previous test will be returned
        myApi.get.mockResolvedValueOnce('FOO');

        // App will call myApi.get() once (again)
        const { container, getByText } = render(<App />);

        // THIS WILL FAIL!
        await waitForElement(
            () => getByText('FOO')
        );

        expect(myApi.get).toHaveBeenCalledTimes(1);

    });


});

__mocks__/myApi.js如下所示:

export default {
  get: jest.fn(() => Promise.resolve({ data: {} }))
};

我了解正在发生的事情:myApi已导入两个测试的共享范围中。这就是.mockResolvedValue*在测试之间应用的原因。

防止这种情况的正确方法是什么?测试应该是原子的,而不是相互耦合的。如果我在get中触发了另一个first test请求,则它应该无法中断second test。太臭了!但是正确的模式是什么?我正在考虑将myApi的不同“副本”克隆到本地测试范围中...但是我担心这会变得很奇怪并导致测试的信心下降。

我发现this question讨论了同一主题,但只说明了为什么会发生这种情况,而不是讨论避免这种情况的正确模式。

package.json

  "dependencies": {
    "axios": "^0.18.1",
    "moment": "^2.24.0",
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-redux": "^7.1.3",
    "react-router-dom": "^5.1.2",
    "react-scripts": "2.1.5",
    "redux": "^4.0.4",
    "redux-thunk": "^2.3.0"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^4.2.3",
    "@testing-library/react": "^9.3.2",
    "redux-mock-store": "^1.5.3",
    "typescript": "^3.7.2"
  }

1 个答案:

答案 0 :(得分:2)

这是我构建测试的方式:

  • 在测试开始时,有一个beforeAll
    • 设置模拟
    • 清除即将测试的模拟功能
    • 渲染组件
    • 调用函数
  • 编写单独的测试用例,每个用例在自己的test / it块中进行描述
    • 允许每个人都有一个更好的,单独的描述
    • 更好地了解在--verbose模式下失败的原因

示例:

App.spec.js
describe("App", () => {
  // jest allows nesting of test suits
  // allowing us to have prettier reporting
  // and having scoped variables
  describe("Api.get returning FOO", () => {
    // define variables used in the test suit
    let wrapper;
    // having the setup here
    beforeAll(async () => {
      Api.get.mockClear();
      Api.get.mockResolvedValue("FOO");
      const { container, getByText } = render(<App />);
      // expose the container to the scope
      wrapper = container;

      await waitForElement(() => getByText("FOO"));
    });

    // write the test cases balow
    // each assertion in a separate test block
    test("should call the Api once", () => {
      expect(Api.get).toHaveBeenCalledOnce();
    });

    test("should have been called with data", () => {
      expect(Api.get).toHaveBeenCalledWith({ x: "y" });
    });

    test("should match the snapshot", () => {
      expect(wrapper).toMatchSnapshot();
    });
  });

  describe("Api.get returning BAR", () => {
    // define variables used in the test suit
    let wrapper;

    beforeAll(async () => {
      Api.get.mockClear();
      Api.get.mockResolvedValue("BAR");
      const { container, getByText } = render(<App />);
      // expose the container to the scope
      wrapper = container;

      await waitForElement(() => getByText("FOO"));
    });

    test.todo("describe what is supposed to happen with Api.get");
    test.todo("describe what is supposed to happen container");
  });
});

回到问题-是的,将在整个测试文件中使用模拟功能,但是如果您尚未使用mockResolvedValueOnce(泄漏到下一个测试中),则上述测试之一案例将失败,或者您的笔试成绩不佳。


编辑:

  

作为思想实验,您能想到可以“解决”该问题的结构吗?

要在每次测试后删除返回的模拟值和实现

afterEach(() => {
  jest.resetAllMocks()
});