What is the best way to test that an async call within componentDidMount
sets the state for a React component? For context, the libraries I'm using for testing are Mocha
, Chai
, Enzyme
, and 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.
});
});
Here are the steps you need to follow for using async/await in React: configure babel. put the async keyword in front of componentDidMount. use await in the function's body.
You may call setState() immediately in componentDidMount() . It will trigger an extra rendering, but it will happen before the browser updates the screen.
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
.
Here is how to do that: (I had to modify it a bit for Typescript with Tslint, but you'll get the idea)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With