Overview
In this 4th post in this series we are going to look at writing tests for more complex asynchronous call patterns.
NOTE: the starting point code for this post is located here: https://github.com/RyanAtViceSoftware/react-testing-best-practices/tree/step-5-adding-real-async-call
Increasing the Complexity of Our Requirements
To demonstrate testing more complex asyc patterns we are going to update our code for the following requirements. When a user opens the app:
- They will be shown a username text box and a
Get Posts
button - After entering a username and clicking the
Get Posts
button we will see a loading indicator - They will then be shown only the post made by the username that was entered
Below you can see how this will look.
Additionally, if the username isn’t found then they will be shown an error message as shown below.
Implementing Our More Complex Async Requirements
To satisfy these new requirements we have to update our async code to do the following:
- Call
/users?username=Bret
to query the the users profile - Pass the returned profile’s ID to
/posts?userid=394
- Handle errors anywhere in the sequence
To accomplish this we will update our code in the following ways:
- Refactor our
getPosts()
method as shown belowgetPosts(e) { e.preventDefault(); this.setState({ fetching: true }); getUserByUserName(this.input.value) .then(user => user.length && user[0].id) .then(getPostsByUserId) .then(posts => this.setState({ posts: posts, fetching: false, error: null })) .catch(error => { this.setState({error, fetching: false}); }); }
This code uses promise chaining to (1) on line 6 we get the users by user name (2) on line 7 we map the result so we can pass the user’s id to the next promise in the chain (3) on line 8 we get the posts by the user’s Id (4) on line 9 we update our state with the new posts (5) on line 14 we handle any error that happens in the previous 4 steps
- We add the
getUsersByUserName()
method as shown belowfunction getUserByUserName(username) { return http.get( '/users', { params: { username } }); }
We are passing an object literal as our second argument on line 4 which specifies a
params
property. Axios will take theparams
and convert the properties to query string arguments which results in our url having/users?username=Bret
- We add the
getPostsByUserId()
method which is shown below that will get passed the user ID that is returned by thegetPostsByUserId()
function as part of our function chain configuration abovefunction getPostsByUserId(userId) { if (!userId) { return Promise.reject('User not found'); } return http.get( '/posts', { params: { userId } } ); }
- Next we need to update our render method to support the new requirements as shown below
render() { return ( <div className="App"> <input type="text" placeholder="Username" ref={(input) => this.input = input} /> <button onClick={this.getPosts}>Get Posts</button> <br/> {this.state.error && <p style={{color: 'red'}}>{this.state.error}</p>} {this.state.fetching && <h3>Loading...</h3>} <ul> {this.state.posts.map( p => <li key={p.id}>{`${p.userId}: ${p.title}`}</li>) } </ul> </div> ); }
Here we’ve (1) on line 4 added a text
input
for collecting the user input using the uncontrolled component approach (2) on line 9 add about button that will call ourgetPosts()
method we added earlier (3) on line 11 we add code to write out an error message if one is defined onthis.state.error
(4) on line 15 we update the map method to write out the user ID with the post so that it’s easier to verify that we are filtering correctly.
NOTE: there are some other minor code updates that are needed to make the code run but we are going to leave those as an exercise to the reader or you can simply pull the git branch above.
If we now run our tests we will see the output below.
We now have two tests failing and this is what we would expect and want as our requirements have changed. We no longer load data when the page first first loads so it makes sense that our loading indicator test would fail. And it would follow that the test verifying that posts load when the page load would also fail.
In our next post we will look at updating our tests for the new code and testing the more complex orchestration logic