如何将Jest模拟函数的范围限制为单个测试 [英] How to limit the scope of Jest mocked functions to a single test

查看:239
本文介绍了如何将Jest模拟函数的范围限制为单个测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm writing functional tests using Jest + Testing-Library/React. After days of head scratching, I figured out that when you use .mockResolvedValue(...) or .mockResolvedValueOnce(...) the scope of the mocking is not limited to that test...

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*适用于跨"测试的原因.

I understand what is happening: myApi is imported into the shared scope of both tests. And this is why the .mockResolvedValue* applies "across" the tests.

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

What is the right way to prevent this? Tests should be atomic, and not coupled to one another. If I trigger another get request in first test it should not be able to break second test. That's smelly! But what's the correct pattern? I'm thinking about cloning distinct "copies" of myApi into the local test scopes... but I worry that will get weird and lead to decreases the confidence of my tests.

我发现了这个问题,它讨论了同一主题,但仅作了解释为什么会发生这种情况,而不是讨论避免这种情况的正确模式.

I found this question which discusses the same topic, but only explains why this happens rather than discussing the right pattern to avoid it.

package.json

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"
  }

推荐答案

这是我构建测试的方式:

This is how I structure my tests:

  • 测试服开头的beforeAll
    • 设置模拟
    • 清除即将测试的模拟功能
    • 渲染组件
    • 调用函数
    • Having a beforeAll block in the beginning of the test suit for
      • setting up the mocks
      • clearing the mock functions that are about to be tested
      • rendering the components
      • calling the functions
      • 允许每个人都有一个更好的,单独的描述
      • 更好地了解在--verbose模式下失败的原因
      • allowing each to have a better,separate description
      • providing better visibility on what has failed in --verbose mode

      示例:

      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(泄漏到下一个测试中),则上述测试用例之一将被使用失败或您的笔试成绩不佳.

      And back to the question - yes, the mock function will be used through the entire test file, but if you have mockResolvedValueOnce that has not been consumed (leaked into the next test) either one of the above test cases will fail or you have a poorly written tests.

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

      As a thought experiment, can you think of a structure that would "solve" that?

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

      To remove return mocked values and implementations after each test you can use

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

      这篇关于如何将Jest模拟函数的范围限制为单个测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆