[英]How to TEST async calls made in componentDidMount that set the state of React Component
What is the best way to test that an async call within componentDidMount
sets the state for a React component? 测试componentDidMount
中的异步调用设置React组件状态的最佳方法是什么? For context, the libraries I'm using for testing are Mocha
, Chai
, Enzyme
, and Sinon
. 对于上下文,我用于测试的库是Mocha
, Chai
, Enzyme
和Sinon
。
Here's an example code: 这是一个示例代码:
/*
* assume a record looks like this:
* { id: number, name: string, utility: number }
*/
// asyncComponent.js
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
// assume that I'm using a library like `superagent` to make ajax calls that returns Promises
request.get('/some/url/that/returns/my/data').then((data) => {
this.setState({
records: data.records
});
});
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", () => {
// I'm thinking of using a library like `nock` to mock the http request
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// NOW WHAT? This is where I'm stuck.
});
});
So, what you are really trying to test is that based on some mock data it "should render correctly ..." . 所以,你真正想要测试的是基于一些模拟数据它“应该正确呈现......” 。
As some people pointed out, a good way to achieve that is by placing the data fetching logic into a separate container and have a "dumb" presentation component that only knows how to render props
. 正如一些人指出的那样,实现这一目标的一个好方法是将数据获取逻辑放入一个单独的容器中,并拥有一个只知道如何渲染props
的“哑”表示组件。
Here is how to do that: (I had to modify it a bit for Typescript with Tslint, but you'll get the idea) 以下是如何做到这一点:(我必须使用Tslint对Typescript进行一些修改,但你会得到这个想法)
export interface Props {
// tslint:disable-next-line:no-any
records: Array<any>;
}
// "dumb" Component that converts props into presentation
class MyComponent extends React.Component<Props> {
// tslint:disable-next-line:no-any
constructor(props: Props) {
super(props);
}
render() {
return (
<div className="async_component">
{this._renderList()}
</div>
);
}
_renderList() {
// tslint:disable-next-line:no-any
return this.props.records.map((record: any) => {
return (
<div className="record" key={record.name}>
<p>{record.name}</p>
<p>{record.utility}</p>
</div>
);
});
}
}
// Container class with the async data loading
class MyAsyncContainer extends React.Component<{}, Props> {
constructor(props: Props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
fetch('/some/url/that/returns/my/data')
.then((response) => response.json())
.then((data) => {
this.setState({
records: data.records
});
});
}
// render the "dumb" component and set its props
render() {
return (<MyComponent records={this.state.records}/>);
}
}
Now you can test MyComponent
rendering by giving your mock data as props. 现在,您可以通过将模拟数据作为道具来测试MyComponent
渲染。
Ignoring the, sane, advice to think again about the structure, one way to go about this could be: 忽视,理智,建议再次考虑结构,一种方法可以是:
mount
function 使用Enzyme的mount
功能 done
callback 让你的休息功能使用done
回调 setImmediate
), this will make sure your promise is resolved 稍等一下(使用setImmediate
fx),这将确保您的承诺得到解决 So, in short: 简而言之:
// asyncComponentTests.js
describe("Async Component Tests", () => {
it("should render correctly after setState in componentDidMount executes", (done) => {
nock("http://some.url.com")
.get("/some/url/that/returns/my/data")
.reply(200, {
data: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
});
const wrapper = mount(<AsyncComponent />);
// make sure state isn't there yet
expect(wrapper.state).to.deep.equal({});
// wait one tick for the promise to resolve
setImmediate(() => {
expect(wrapper.state).do.deep.equal({ .. the expected state });
done();
});
});
});
Note: 注意:
I have no clue about nock, so here I assume your code is correct 我对nock一无所知,所以在这里我假设你的代码是正确的
IMO, this is actually a common issue which appears more complicated because of promises and componentDidMount
: You're trying to test a functions which are only defined within the scope of another function. IMO,这实际上是一个常见的问题,因为promises和componentDidMount
看起来更复杂:你试图测试只在另一个函数范围内定义的函数。 ie You should split your functions out and test them individually. 即你应该将你的功能分开并单独测试它们。
Component 零件
class AsyncComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
};
}
componentDidMount() {
request.get('/some/url/that/returns/my/data')
.then(this._populateState);
}
render() {
return (
<div className="async_component">
{ this._renderList() }
</div>
);
}
_populateState(data) {
this.setState({
records: data.records
});
}
_renderList() {
return this.state.records.map((record) => {
return (
<div className="record">
<p>{ record.name }</p>
<p>{ record.utility }</p>
</div>
);
});
}
}
Unit Test 单元测试
// asyncComponentTests.js
describe("Async Component Tests", () => {
describe("componentDidMount()", () => {
it("should GET the user data on componentDidMount", () => {
const data = {
records: [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
]
};
const requestStub = sinon.stub(request, 'get').resolves(data);
sinon.spy(AsyncComponent.prototype, "_populateState");
mount(<AsyncComponent />);
assert(requestStub.calledOnce);
assert(AsyncComponent.prototype._populateState.calledWith(data));
});
});
describe("_populateState()", () => {
it("should populate the state with user data returned from the GET", () => {
const data = [
{ id: 1, name: "willson", utility: 88 },
{ id: 2, name: "jeffrey", utility: 102 }
];
const wrapper = shallow(<AsyncComponent />);
wrapper._populateState(data);
expect(wrapper.state).to.deep.equal(data);
});
});
});
Note : I've written the unit tests from documentation alone, so the use of shallow
, mount
, assert
, and expect
might not be best practices. 注意 :我单独从文档编写单元测试,因此使用shallow
, mount
, assert
和expect
可能不是最佳实践。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.