我有一个组件:
RandomGif.js
import React, { Component } from "react";
import Gif from "./Gif";
import Loader from "./library/Loader";
import { fetchRandom } from "../resources/api";
class RandomGif extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
state = {
loading: false,
gif: null
};
componentDidMount() {
this.handleClick();
}
async handleClick() {
let gifContent = null;
try {
this.setState({
loading: true
});
const result = await fetchRandom();
if (!!result && result.data) {
gifContent = {
id: result.data.id,
imageUrl: result.data.images.downsized_large.url,
staticImageUrl: result.data.images.downsized_still.url,
title: result.data.title
};
}
} catch (e) {
console.error(e);
} finally {
this.setState({
loading: false,
gif: gifContent
});
}
}
render() {
const { gif, loading } = this.state;
const showResults = gif && !loading;
return (
<div className="random">
{!showResults && <Loader />}
<button className="btn" onClick={this.handleClick}>
RANDOMISE
</button>
{showResults && <Gif data={gif} />}
</div>
);
}
}
export default RandomGif;
如果我直接从此组件的实例调用方法,则可以成功测试状态是否正在更新。但是,如果我模拟按钮单击,则不会更新任何内容,并且测试失败。我尝试了setImmediate
和setTimeout
的技巧,但这些技巧不起作用。
到目前为止,我还无法编写以下测试用例:
这是我到目前为止提出的。
RandomGif.spec.js
import React from "react";
import { shallow, mount } from "enzyme";
import RandomGif from "./RandomGif";
describe("Generate Random Gif", () => {
it("should render correctly.", () => {
const wrapper = shallow(<RandomGif />);
expect(wrapper).toMatchSnapshot();
});
it("should load a random GIF on calling handleSearch fn.", async () => {
const wrapper = mount(<RandomGif />);
const instance = wrapper.instance();
expect(wrapper.state("gif")).toBe(null);
await instance.handleClick();
expect(wrapper.state("gif")).not.toBe(null);
});
it("THIS TEST FAILS!!!", () => {
const wrapper = mount(<RandomGif />);
expect(wrapper.state("gif")).toBe(null);
wrapper.find('button').simulate('click');
wrapper.update()
expect(wrapper.state("gif")).not.toBe(null);
});
});
api.py
export const fetchRandom = async () => {
const url = `some_url`;
try {
const response = await fetch(url);
return await response.json();
} catch (e) {
console.error(e);
}
return null;
};
请帮助我找出称为“前端测试”的难题的缺失部分。
答案 0 :(得分:1)
fetchRandom
,以便在测试期间不会发送任何实际请求。import { fetchRandom } from "../resources/api";
jest.mock("../resources/api"); // due to automocking fetchRandom is jest.fn()
// somewhere in the it()
fetchRandom.mockReturnValue(Promise.resolve({ data: { images: ..., title: ..., id: ...} }))
setTimeout
或await <anything>
来使组件的代码实现此Promise已解决。全部与microtasks/macrotasks queue有关。 wrapper.find('button').simulate('click');
await Promise.resolve();
// component has already been updated here
或
it("test something" , (done) => {
wrapper.find('button').simulate('click');
setTimeout(() => {
// do our checks on updated component
done();
}); // 0 by default, but it still works
})
顺便说一句,您已经做到了
await instance.handleClick();
但对我来说,它看起来和说的一样神奇
await 42;
此外,它还可以工作(查看微任务/宏任务上的链接),我认为这会使测试的可读性更差(“ handleClick返回需要等待什么?”)。因此,我建议使用繁琐但不太混乱的await Promise.resolve();
甚至是await undefined;
state
并直接调用实例方法都是反模式。只是一句话(我完全同意Kent C. Dodds的引用):总而言之,如果您的测试使用instance()或state(),请知道您正在测试用户可能不了解甚至不在意的事情,这将使您对测试的信心进一步提高当您的用户使用它们时,一切都会起作用。
让我们检查渲染结果:
import Loader from "./library/Loader";
...
wrapper.find('button').simulate('click');
expect(wrapper.find(Loader)).toHaveLength(1);
await Promise.resolve();
expect(wrapper.find(Loader)).toHaveLength(1);
expect(wrapper.find(Gif).prop("data")).toEqual(data_we_mocked_in_mock)
让我们完全明白:
import {shallow} from "enzyme";
import Gif from "./Gif";
import Loader from "./library/Loader";
import { fetchRandom } from "../resources/api";
jest.mock( "../resources/api");
const someMockForFetchRandom = { data: { id: ..., images: ..., title: ... }};
it("shows loader while loading", async () => {
fetchRandom.mockReturnValue(Promise.resolve(someMockForFetchRandom));
const wrapper = shallow(<RandomGif />);
expect(wrapper.find(Loader)).toHaveLength(0);
wrapper.find('button').simulate('click');
expect(wrapper.find(Loader)).toHaveLength(1);
await Promise.resolve();
expect(wrapper.find(Loader)).toHaveLength(0);
});
it("renders images up to response", async () => {
fetchRandom.mockReturnValue(Promise.resolve(someMockForFetchRandom));
const wrapper = shallow(<RandomGif />);
wrapper.find('button').simulate('click');
expect(wrapper.find(Gif)).toHaveLength(0);
await Promise.resolve();
expect(wrapper.find(Gif).props()).toEqual( {
id: someMockForFetchRandom.data.id,
imageUrl: someMockForFetchRandom.data.images.downsized_large.url,
staticImageUrl: someMockForFetchRandom.data.images.downsized_still.url,
title: someMockForFetchRandom.data.title
});
});