Reactjs 如何在react组件中测试api调用,并在api调用成功后预期视图更改?
我有一个简单的react组件,只有一个按钮,当单击该按钮时,它使用fetch进行api调用,在成功调用之后,调用setState来更新组件 在my-button.jsx文件中Reactjs 如何在react组件中测试api调用,并在api调用成功后预期视图更改?,reactjs,jestjs,enzyme,Reactjs,Jestjs,Enzyme,我有一个简单的react组件,只有一个按钮,当单击该按钮时,它使用fetch进行api调用,在成功调用之后,调用setState来更新组件 在my-button.jsx文件中 import React from "react"; export default class MyButton extends React.Component { constructor(props) { super(props); this.state = {
import React from "react";
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null
}
this.getUser = this.getUser.bind(this);
}
async getUser() {
try {
const res = await fetch("http://localhost:3000/users");
if (res.status >= 400)
throw new Error("something went wrong");
const user = await res.json();
this.setState({ user });
} catch (err) {
console.error(err);
}
}
render() {
return (
<div>
<button onClick={this.getUser}>Click Me</button>
{this.state.user ? <p>got user</p> : null}
</div>
)
}
}
使用settimeout将对测试期望强制执行此顺序
getUser
→ <代码>测试预期
在当前的MyButton
实现中,没有一种直接的方法来实现这一点getUser
需要被提取出来并作为道具传递给MyButton
,以便在承诺链上有更好的控制,即getUser
样本
getUser().then(testExpectations)
在重构的第一步中,在按钮onClick
中调用getUser
,代替对组件的shallowRapper
的模拟调用
这是simulate所做的,但它返回一个包装器实例。你不想要这个;您希望从调用getUser
返回承诺,以便链接到它
it("must test the button click", (done) => {
fetch.mockImplementation(() => {
return Promise.resolve({
status: 200,
json: () => Promise.resolve({ name: "Manas", userId: 2 })
});
});
const wrapper = shallow(<MyButton />);
const button = wrapper.find("button");
const onClick = button.prop('onClick');
onClick().then(() => {
wrapper.update();
expect(wrapper.find("p").length).toBe(1);
fetch.mockClear();
done();
})
})
it(“必须测试按钮点击”,(完成)=>{
fetch.mockImplementation(()=>{
还愿({
现状:200,
json:()=>Promise.resolve({name:“Manas”,userId:2})
});
});
常量包装器=浅();
const button=wrapper.find(“按钮”);
const onClick=button.prop('onClick');
onClick()。然后(()=>{
wrapper.update();
expect(wrapper.find(“p”).length).toBe(1);
fetch.mockClear();
完成();
})
})
重构的下一步是将
getUser
作为属性转发到MyButton
。如果您发现MyButton
总是将该特定实现用于其click事件处理程序,则可能不需要这样做。如果在测试运行期间有异步调用,则必须在事件循环结束时运行断言/期望
it('must test the button click', done => {
fetch.mockImplementation(() => {
return Promise.resolve({
status: 200,
json: () => {
return Promise.resolve({ name: 'Manas', userId: 2 });
}
});
});
const wrapper = shallow(<MyButton />);
wrapper.find('button').simulate('click'); // async invocation
// wait till async action is done
new Promise((resolve, reject) => {
setImmediate(() => {
resolve();
}, 0);
}).then(() => {
wrapper.update(); // you probably won't need this line
expect(wrapper.find('p').length).toBe(1);
fetch.mockClear();
done();
});
});
it('必须测试按钮点击',完成=>{
fetch.mockImplementation(()=>{
还愿({
现状:200,
json:()=>{
返回Promise.resolve({name:'Manas',userId:2});
}
});
});
常量包装器=浅();
wrapper.find('button').simulate('click');//异步调用
//等待异步操作完成
新承诺((解决、拒绝)=>{
setImmediate(()=>{
解决();
}, 0);
}).然后(()=>{
wrapper.update();//您可能不需要这一行
expect(wrapper.find('p').length).toBe(1);
fetch.mockClear();
完成();
});
});
在我的所有项目中,我通常将其作为一个util方法编写出来
// test-helper.js
export const waitForAsyncActionsToFinish = () => {
return new Promise((resolve, reject) => {
setImmediate(() => {
resolve();
}, 0);
});
};
it('test something', (done) => {
// mock some async actions
const wrapper = shallow(<Component />);
// componentDidMount async actions
waitForAsyncActionsToFinish().then(() => {
wrapper.find('.element').simulate('click');
// onClick async actions - you have to wait again
waitForAsyncActionsToFinish().then(() => {
expect(wrapper.state.key).toEqual('val');
done();
});
});
});
//test-helper.js
导出常量waitForAsyncActionsToFinish=()=>{
返回新承诺((解决、拒绝)=>{
setImmediate(()=>{
解决();
}, 0);
});
};
它('测试某物',(完成)=>{
//模拟一些异步操作
常量包装器=浅();
//componentDidMount异步操作
waitForAsyncActionsToFinish()。然后(()=>{
wrapper.find('.element').simulate('click');
//onClick异步操作-您必须再次等待
waitForAsyncActionsToFinish()。然后(()=>{
expect(wrapper.state.key).toEqual('val');
完成();
});
});
});
如果不使用settimeout会发生什么情况?测试失败预期1收到0如果没有“settimeout”,您可以共享您收到的错误吗?我已经用失败的测试更新了它,但我的测试失败预期1收到相同的错误0Hmm,让我调查这要求代码的所有异步部分(模拟为)使用等于0的超时进行解析,以便最后解析由waitForAsyncActionsToFinish返回的承诺。模拟并立即返回所有异步操作可能并不总是合适的,例如,当渲染包括动画,并且需要延迟以检查结果时。setImmediate
在事件循环结束时添加新的异步调用。因此,基本上我们在所有异步调用完成后解决。但你是对的,这可能不适用于超时。这就是我们开始使用jest计时器或sinon计时器的时候。OPs问题更侧重于无超时的异步操作。这应该可以解决OP面临的问题。你的回答我觉得有点不对劲,它只是用setImmediate替换了setTimeout
it('must test the button click', done => {
fetch.mockImplementation(() => {
return Promise.resolve({
status: 200,
json: () => {
return Promise.resolve({ name: 'Manas', userId: 2 });
}
});
});
const wrapper = shallow(<MyButton />);
wrapper.find('button').simulate('click'); // async invocation
// wait till async action is done
new Promise((resolve, reject) => {
setImmediate(() => {
resolve();
}, 0);
}).then(() => {
wrapper.update(); // you probably won't need this line
expect(wrapper.find('p').length).toBe(1);
fetch.mockClear();
done();
});
});
// test-helper.js
export const waitForAsyncActionsToFinish = () => {
return new Promise((resolve, reject) => {
setImmediate(() => {
resolve();
}, 0);
});
};
it('test something', (done) => {
// mock some async actions
const wrapper = shallow(<Component />);
// componentDidMount async actions
waitForAsyncActionsToFinish().then(() => {
wrapper.find('.element').simulate('click');
// onClick async actions - you have to wait again
waitForAsyncActionsToFinish().then(() => {
expect(wrapper.state.key).toEqual('val');
done();
});
});
});