测试从componentDidMount
(或useEffect
- 请参阅此答案)运行异步函数的组件必须在运行断言之前等待重新渲染。wrapper.update
用于在运行断言之前同步 Enzyme 的组件树,但不等待Promise或强制重新渲染。
一种方法是使用setImmediate
,它在事件循环结束时运行它的回调,允许 Promise 非侵入性地解析。
这是您的组件的最小示例:
import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
import mockAxios from "axios";
import HelloWorld from "./HelloWorld";
jest.mock("axios");
describe("HelloWorld", () => {
beforeEach(() => jest.resetAllMocks());
it("should call `axios.get` and set the response to `state.data`", async () => {
const mockData = ["foo", "bar", "baz"];
mockAxios.get.mockImplementationOnce(() => Promise.resolve({data: mockData}));
const wrapper = mount(<HelloWorld />);
await new Promise(setImmediate);
wrapper.update();
expect(mockAxios.get).toHaveBeenCalledTimes(1);
expect(wrapper.instance().state.data).toEqual(mockData);
});
});
虽然这有效,wrapper.instance().state.data
但与应用程序的内部状态过于相关。我更愿意编写一个对行为而不是实现进行断言的测试;变量名称更改不应强制重新编写测试。
这是一个示例组件,其中更新的数据在 DOM 中呈现并以更黑盒的方式进行测试:
组件 ( StackUsers.js
):
import axios from "axios";
import React from "react";
export default class StackUsers extends React.Component {
constructor(props) {
super(props);
this.state = {users: null};
}
componentDidMount() {
this.getUsers();
}
async getUsers() {
const ids = this.props.ids.join(";");
const url = `https://api.stackexchange.com/2.2/users/${ids}?site=stackoverflow`;
const res = await axios.get(url);
this.setState({users: res.data.items});
}
render() {
const {users} = this.state;
return (
<>
{users
? <ul data-test="test-stack-users-list">{users.map((e, i) =>
<li key={i}>{e.display_name}</li>
)}</ul>
: <div>loading...</div>
}
</>
);
}
}
测试 ( StackUsers.test.js
):
import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
import mockAxios from "axios";
import StackUsers from "../src/components/StackUsers";
jest.mock("axios");
describe("StackUsers", () => {
beforeEach(() => jest.resetAllMocks());
it("should load users", async () => {
mockAxios.get.mockImplementationOnce(() => Promise.resolve({
data: {
items: [
{"display_name": "Jeff Atwood"},
{"display_name": "Joel Spolsky"},
]
},
status: 200
}));
const wrapper = mount(<StackUsers ids={[1, 4]} />);
let users = wrapper
.find('[data-test="test-stack-users-list"]')
.hostNodes()
;
expect(users.exists()).toBe(false);
await new Promise(setImmediate);
wrapper.update();
expect(mockAxios.get).toHaveBeenCalledTimes(1);
users = wrapper
.find('[data-test="test-stack-users-list"]')
.hostNodes()
;
expect(users.exists()).toBe(true);
expect(users.children()).toHaveLength(2);
expect(users.children().at(0).text()).toEqual("Jeff Atwood");
expect(users.children().at(1).text()).toEqual("Joel Spolsky");
});
});
现在,测试知道的唯一事情axios.get
就是使用它,结果应该出现在具有特定data-test
属性的元素中。这不会过度干扰 CSS 类、HTML 结构或组件内部。
作为参考,这是我的依赖项:
{
"dependencies": {
"axios": "^0.18.0",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
"devDependencies": {
"enzyme": "3.9.0",
"enzyme-adapter-react-16": "1.12.1",
"jest": "24.7.1",
"jest-environment-jsdom": "24.7.1"
}
}