High ROI Testing with React, Enzyme and Jest – Part 4, Testing More Complex Async Code

By Ryan Vice | Posted on March 13, 2018

React| Testing

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:

  1. They will be shown a username text box and a Get Posts button
  2. After entering a username and clicking the Get Posts button we will see a loading indicator
  3. 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:

  1. Call /users?username=Bret to query the the users profile
  2. Pass the returned profile’s ID to /posts?userid=394
  3. Handle errors anywhere in the sequence

To accomplish this we will update our code in the following ways:

  1. Refactor our getPosts() method as shown below
    getPosts(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

  2. We add the getUsersByUserName() method as shown below
    function 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 the params and convert the properties to query string arguments which results in our url having /users?username=Bret

  3. We add the getPostsByUserId() method which is shown below that will get passed the user ID that is returned by the getPostsByUserId()  function as part of our function chain configuration above
    function getPostsByUserId(userId) {
      if (!userId) {
        return Promise.reject('User not found');
      }
    
      return http.get(
        '/posts', {
          params: {
            userId
          }
        }
      );
    }
  4. 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 our getPosts() method we added earlier (3) on line 11 we add code to write out an error message if one is defined on this.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

 

Watch Video Series

5 Keys to Success Before Building Your Software

You may have considered getting a custom software system to help your business run more smoothly, but how do you get started? We will look at 5 factors that we feel are critical to success when building a custom software system.

Need an expert team of designers, mobile developers, web developers and back-end developers?

Discover Vice Software