我想模拟一个已记忆的 React 组件。我正在使用 React 18.2 和 Jest 29.7 以及 Typescript,以防万一。我已经尝试了几种方法,但似乎找不到有效的方法。
这是我从哪里开始的一个最小示例:
// Foo.tsx
import Bar from "./Bar";
export default function Foo() {
return <Bar />;
}
// Bar.tsx
import { memo } from "react";
function Bar() {
return <div>original</div>;
}
export default memo(Bar);
//Foo.test.tsx
/**
* @jest-environment jsdom
*/
import "@testing-library/jest-dom";
import Foo from "./Foo";
import { render, screen } from "@testing-library/react";
const barModule = require("./Bar");
describe("Foo", () => {
beforeEach(() => {
jest
.spyOn(barModule, "default")
.mockImplementation(() => <div>mocked</div>);
});
it("should mock Bar", () => {
render(<Foo />);
expect(screen.queryByText("mocked")).toBeInTheDocument();
});
});
当Bar没有被记忆时,测试成功。但是通过调用备忘录,我收到此错误消息:
Cannot spy on the `default` property because it is not a function; object given instead.
互联网很快告诉我,我应该导出非记忆组件并模拟该组件。所以我这样做了(Foo.tsx 不变):
// Bar.tsx
import { memo } from "react";
export function Bar_() {
return <div>original</div>;
}
export default memo(Bar_);
//Foo.test.tsx
/**
* @jest-environment jsdom
*/
import "@testing-library/jest-dom";
import Foo from "./Foo";
import { render, screen } from "@testing-library/react";
const barModule = require("./Bar");
describe("Foo", () => {
beforeEach(() => {
jest
.spyOn(barModule, "Bar_")
.mockImplementation(() => <div>mocked</div>);
});
it("should mock Bar", () => {
render(<Foo />);
expect(screen.queryByText("mocked")).toBeInTheDocument();
});
});
这会失败,因为由于某种原因没有应用模拟。使用原始的 Bar_ 实现。
互联网还建议模拟整个模块,所以我尝试了这个(其他两个文件不变):
//Foo.test.tsx
/**
* @jest-environment jsdom
*/
import "@testing-library/jest-dom";
import Foo from "./Foo";
import { render, screen } from "@testing-library/react";
describe("Foo", () => {
beforeEach(() => {
jest.mock("./Bar", () => {
const originalModule = jest.requireActual("./Bar");
return {
...originalModule,
Bar_: () => <div>Baba</div>,
};
});
});
it("should mock Bar", () => {
render(<Foo />);
screen.debug();
expect(screen.queryByText("mocked")).toBeInTheDocument();
});
});
由于此错误而失败:
The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: _jsxFileName
我知道通常可以通过将变量重命名为以“mock”开头的名称来避免此错误。但这 _jsxFileName 是什么?
我在输入此内容时实际上已经想到了这一点。无论如何,我会保留这一步,以防有人也被困在这一点上。因此,我通过将模拟组件提取到名称以“Mock”开头的变量来修复此问题。现在我有这个:
//Foo.test.tsx
/**
* @jest-environment jsdom
*/
import "@testing-library/jest-dom";
import Foo from "./Foo";
import { render, screen } from "@testing-library/react";
describe("Foo", () => {
beforeEach(() => {
const MockBar_ = () => <div>Baba</div>;
jest.mock("./Bar", () => {
const originalModule = jest.requireActual("./Bar");
return {
...originalModule,
Bar_: MockBar_,
};
});
});
it("should mock Bar", () => {
render(<Foo />);
screen.debug();
expect(screen.queryByText("mocked")).toBeInTheDocument();
});
});
这再次失败,因为没有应用模拟,而是使用了原始的 Bar_ 组件。
那么你实际上是如何做到这一点的呢? :-D
jest.replaceProperty(object, propertyKey, value)
将 object[propertyKey]
替换为 value
。
例如
Bar.tsx
:
import React, { memo } from 'react';
function Bar() {
return <div>original</div>;
}
export default memo(Bar);
Foo.tsx
:
import React from 'react';
import Bar from './Bar';
export default function Foo() {
return <Bar />;
}
Foo.test.tsx
:
/**
* @jest-environment jsdom
*/
import React from 'react';
import '@testing-library/jest-dom';
import Foo from './Foo';
import { render, screen } from '@testing-library/react';
const barModule = require('./Bar');
describe('Foo', () => {
it('should mock Bar', () => {
jest.replaceProperty(barModule, 'default', () => <div>mocked</div>);
render(<Foo />);
expect(screen.queryByText('mocked')).toBeInTheDocument();
});
});
测试结果:
PASS stackoverflow/77770830/Foo.test.tsx
Foo
√ should mock Bar (17 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.11 s
Ran all test suites related to changed files.
套装版本:
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.2",
"react": "^18.2.0",