【问题标题】:Create React App doesn't properly mock modules from __mocks__ directoryCreate React App 不能正确地模拟 __mocks__ 目录中的模块
【发布时间】:2021-04-14 00:32:18
【问题描述】:

我有一个来自__mocks__ 目录的 Jest 和模拟的工作示例:

使用简单的 Jest 设置

// package.json
{
  "name": "a",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  ...
  "devDependencies": {
    "jest": "^26.6.3"
  },
  "dependencies": {
    "@octokit/rest": "^18.0.12"
  }
}

然后/index.js

const { Octokit } = require("@octokit/rest");

const octokit = new Octokit();

module.exports.foo = function() {
  return octokit.repos.listForOrg({ org: "octokit", type: "public" })
}

通过测试 (/index.test.js):

const { foo } = require("./index.js");

test("foo should be true", async () => {
    expect(await foo()).toEqual([1,2]);
});

和模拟(/__mocks__/@octokit/rest/index.js):

module.exports.Octokit = jest.fn().mockImplementation( () => ({
    repos: {
        listForOrg: jest.fn().mockResolvedValue([1,2])
    }
}) );

这很好用并且测试通过了。

使用创建 React 应用程序

然而,用 Create React App 做同样的事情似乎给了我一个奇怪的结果:

// package.json
{
  "name": "b",
  "version": "0.1.0",
  "dependencies": {
    "@octokit/rest": "^18.0.12",
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.1",
    "web-vitals": "^0.2.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  ...
}

然后/src/foo.js:

import { Octokit } from "@octokit/rest";

const octokit = new Octokit();

module.exports.foo = function() {
  return octokit.repos.listForOrg({ org: "octokit", type: "public" })
}

通过测试 (/src/foo.test.js):

const { foo} = require("./foo.js");

test("foo should be true", async () => {
    expect(await foo()).toEqual([1,2]);
});

同样的模拟(在/src/__mocks__/@octokit/rest/index.js下):

export const Octokit = jest.fn().mockImplementation( () => ({
    repos: {
        listForOrg: jest.fn().mockResolvedValue([1,2])
    }
}) );

这会使测试失败:

 FAIL  src/foo.test.js
  ✕ foo should be true (2 ms)

  ● foo should be true

    expect(received).toEqual(expected) // deep equality

    Expected: [1, 2]
    Received: undefined

      2 |
      3 | test("foo should be true", async () => {
    > 4 |     expect(await foo()).toEqual([1,2]);
        |                         ^
      5 | });
      6 |
      7 |

      at Object.<anonymous> (src/foo.test.js:4:25)

在阅读了很多之后,我似乎无法让 __mocks__ 在 Create React App 中工作。有什么问题?

【问题讨论】:

  • 您似乎在混合模块类型(import/exportrequire/module.exports),我怀疑这是问题的根源 - 您是否尝试过使用调试器单步执行看看执行了什么?
  • 这似乎无法解决问题。我检查了src/foo.test.jsimport { foo } from "./foo.js" 还是不行

标签: jestjs mocking create-react-app


【解决方案1】:

问题在于 CRA 的默认 Jest 设置会自动重置模拟,这会删除您设置的 mockResolvedValue


解决这个问题的一种方法是公开模拟,这也让您可以更好地控制在不同的测试中使用不同的值(例如测试错误处理)并断言它被称为 with也来自模块的功能:

export const mockListForOrg = jest.fn();

export const Octokit = jest.fn().mockImplementation(() => ({
    repos: {
        listForOrg: mockListForOrg,
    },
}));

然后你在测试中配置你想要的值, Jest 会重置它:

import { mockListForOrg } from "@octokit/rest";

import { foo } from "./foo";

test("foo should be true", async () => {
    mockListForOrg.mockResolvedValueOnce([1, 2]);

    expect(await foo()).toEqual([1, 2]);
});

另一种选择是将以下内容添加到您的 package.json 以覆盖该配置,每个 this issue

{
  ...
  "jest": {
    "resetMocks": false
  }
}

不过,这可能会导致在测试之间保留模拟状态(收到的调用)的问题,因此您需要确保它们被清除和/或重置某处。 p>


请注意,您通常不应模拟您不拥有的东西 - 如果@octokit/rest 的接口发生更改,您的测试将继续通过,但您的代码将无法工作。为避免此问题,我会推荐以下两者之一或两者:

  • 将断言移动到传输层,例如使用MSW 检查是否发出了正确的请求;或
  • 编写一个简单的外观来包装@octokit/rest,将您的代码与您不拥有的接口分离,然后模拟它;

连同更高级别(端到端)的测试,以确保一切都与真正的 GitHub API 一起正常工作。

事实上,删除 mocks 并编写这样一个使用 MSW 的测试:

import { rest } from "msw";
import { setupServer } from "msw/node";

import { foo }  from "./foo";

const server = setupServer(rest.get("https://api.github.com/orgs/octokit/repos", (req, res, ctx) => {
    return res(ctx.status(200), ctx.json([1, 2]));
}));

beforeAll(() => server.listen());

afterAll(() => server.close());

test("foo should be true", async () => {
    expect(await foo()).toEqual([1, 2]);
});

表明当前关于 octokit.repos.listForOrg 将返回的假设是不准确的,因为此测试失败:

  ● foo should be true

    expect(received).toEqual(expected) // deep equality

    Expected: [1, 2]
    Received: {"data": [1, 2], "headers": {"content-type": "application/json", "x-powered-by": "msw"}, "status": 200, "url": "https://api.github.com/orgs/octokit/repos?type=public"}

      13 | 
      14 | test("foo should be true", async () => {
    > 15 |     expect(await foo()).toEqual([1, 2]);
         |                         ^
      16 | });
      17 | 

      at Object.<anonymous> (src/foo.test.js:15:25)

您的实现实际上应该看起来更像:

export async function foo() {
  const { data } = await octokit.repos.listForOrg({ org: "octokit", type: "public" });
  return data;
}

或:

export function foo() {
  return octokit.repos.listForOrg({ org: "octokit", type: "public" }).then(({ data }) => data);
}

【讨论】:

  • 很棒的答案!谢谢!
  • @dinchev note 我刚刚更新了它 - 我认为你的实际实现有问题
猜你喜欢
  • 2020-11-06
  • 2021-01-10
  • 1970-01-01
  • 2019-08-17
  • 2019-11-23
  • 2021-08-01
  • 2019-10-02
  • 2020-01-10
  • 1970-01-01
相关资源
最近更新 更多