Create React App 无法正确模拟 __mocks__ 目录中的模块
Create React App doesn't properly mock modules from __mocks__ directory
我在 __mocks__
目录中有一个 Jest 和 mocks 的工作示例:
使用简单的 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])
}
}) );
这很好用并且测试通过了。
使用 Create 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 中工作。有什么问题吗?
问题是 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
}
}
不过,这可能会导致在测试之间保留模拟状态(收到的呼叫)的问题,因此您需要确保它们已被清除 and/or 在某处重置 .
请注意,您通常不应该模拟您不拥有的东西 - 如果 @octokit/rest
的接口发生变化,您的测试将继续通过,但 您的代码将无法工作。为避免此问题,我会推荐其中一个或两个:
- 将断言移至传输层,例如使用MSW 检查是否提出了正确的 请求 ;或者
- 编写一个包装
@octokit/rest
的简单外观,将您的代码与您不拥有的接口解耦,并模拟它;
以及更高级别的(端到端)测试,以确保一切正常工作 GitHub API.
事实上,删除模拟并编写这样一个使用 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
会 return 的假设是不准确的,因为这个测试失败了:
● 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);
}
我在 __mocks__
目录中有一个 Jest 和 mocks 的工作示例:
使用简单的 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])
}
}) );
这很好用并且测试通过了。
使用 Create 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 中工作。有什么问题吗?
问题是 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
}
}
不过,这可能会导致在测试之间保留模拟状态(收到的呼叫)的问题,因此您需要确保它们已被清除 and/or 在某处重置 .
请注意,您通常不应该模拟您不拥有的东西 - 如果 @octokit/rest
的接口发生变化,您的测试将继续通过,但 您的代码将无法工作。为避免此问题,我会推荐其中一个或两个:
- 将断言移至传输层,例如使用MSW 检查是否提出了正确的 请求 ;或者
- 编写一个包装
@octokit/rest
的简单外观,将您的代码与您不拥有的接口解耦,并模拟它;
以及更高级别的(端到端)测试,以确保一切正常工作 GitHub API.
事实上,删除模拟并编写这样一个使用 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
会 return 的假设是不准确的,因为这个测试失败了:
● 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);
}