我有一个只有一个按钮的简单react组件,单击该按钮后,它会使用fetch进行api调用,成功调用是调用setState来更新该组件。
在我的my-button.jsx文件中
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>
)
}
}
在我的测试文件中
import React from "react";
import { shallow, Mount } from "enzyme";
import MyButton from "../my-button";
beforeAll(() => {
global.fetch = jest.fn();
});
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");
//here using setTimeout to delay the find call, How to avoid using setTimeout
setTimeout(() => {
wrapper.update();
expect(wrapper.find("p").length).toBe(1);
fetch.mockClear();
done();
}, 1000)
})
我正在使用setTimeout来延迟期望调用,以避免如何避免使用setTimeout,因为这不是有效的测试方法。
如果我不使用setTimeout,则测试失败
src/App.test.js
FAIL src/components/__test__/my-button.test.js
● must test the button click
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
26 | // setTimeout(() => {
27 | wrapper.update();
> 28 | expect(wrapper.find("p").length).toBe(1);
| ^
29 | fetch.mockClear();
30 | done();
31 | // }, 1000)
at Object.toBe (src/components/__test__/my-button.test.js:28:38)
Test Suites: 1 failed, 1 passed, 2 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
答案 0 :(得分:2)
使用settimeout强制执行此顺序以达到测试期望。
getUser
→testExpectations
对于当前的MyButton
实现,没有直接的方法可以实现。需要提取getUser
并将其作为对MyButton
的支持,以便可以对getUser
<的promise链 ie 链测试期望进行更好的控制/ p>
样品
getUser().then(testExpectations)
在重构的第一步,调用按钮getUser
中的onClick
来代替对组件ShallowWrapper
的模拟调用。
这是Simulation的工作,但它返回一个包装实例。你不要这个您希望通过调用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();
})
})
重构的下一步将是将getUser
作为属性转发到MyButton
。如果您发现MyButton
始终将特定的实现用于其点击事件处理程序,则可能没有必要。
答案 1 :(得分:0)
如果在测试运行期间进行异步调用,则必须在事件循环结束时运行断言/期望。
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();
});
});
通常我在所有项目中都将其写为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();
});
});
});