Pagepro Blog

JavaScript

Effective Unit Testing of React Components (Part 2)

Posted on .

Effective Unit Testing of React Components (Part 2)

Introduction

This time I will show you how defining the component contract looks in practice and how to represent it with actual Jest & Enzyme unit tests.

This is the second part of series on unit testing React components with Jest & Enzyme. If you missed the first part, be sure to check it out first.

As the guinea pig, we will use container component from one of the React documentation’s example projects: Emoji Search.

You can check the app demo here.

I have made some adjustments to fit the needs of this article, so to check the source code, head to my fork on github.

Mentioned container component is called App (what a surprise!), check its code:


import React, { PureComponent } from "react";
import Header from "./Header";
import SearchInput from "./SearchInput";
import EmojiResults from "./EmojiResults";
import filterEmoji from "./filterEmoji";
import doAsyncCall from "./doAsyncCall";

class App extends PureComponent {
 constructor(props) {
   super(props);
   this.state = {
     filteredEmoji: [],
     status: null,
     maxResults: 20
   };
 }

 componentDidMount() {
   doAsyncCall().then(() => {
     this.setState({ filteredEmoji: filterEmoji("", this.state.maxResults) });
   });
 }

 handleSearchChange = event => {
   this.setState({
     filteredEmoji: filterEmoji(event.target.value, this.state.maxResults)
   });
 };

 render() {
   return (
     
<Header /> <SearchInput textChange={this.handleSearchChange} /> <EmojiResults emojiData={this.state.filteredEmoji} />
); } } export default App;

Without further ado, let’s define the contract of this component with the help of the questions that I proposed in the previous part.

Defining contract

What component render()s?


render() {
   return (
     
<Header /> <SearchInput textChange={this.handleSearchChange} /> <EmojiResults emojiData={this.state.filteredEmoji} />
); }

As is the case with most of the containers, the rendered content is wrapped with a div.
App always renders three children components: Header, SearchInput and EmojiResults.

It important to mention that we have to give special treatment to components that use conditional rendering. If you write the contract of such a component, list each branch with the consideration of the required condition and what will be rendered if it’s met.

What component shares with its children as a prop?


class App extends PureComponent {
 constructor(props) {
   super(props);
   this.state = {
     filteredEmoji: [],
     status: null,
     maxResults: 20
   };
 }

 handleSearchChange = event => {
   this.setState({
     filteredEmoji: filterEmoji(event.target.value, this.state.maxResults)
   });
 };

render() {
   return (
     
<Header /> <SearchInput textChange={this.handleSearchChange} /> <EmojiResults emojiData={this.state.filteredEmoji} />
); }

Let’s take an even closer look at render().

So Header is a strong, independent component. It doesn’t have any expectations data-wise from his parent.

Yet Header siblings will provide us with some room to put work in.

SearchInput is waiting for this.handleSearchChange as a textChange prop. Meanwhile, EmojiResults needs this.state.filteredEmoji passed as emojiData.

How component (and its children) interacts with the user?

App component doesn’t directly handle any events but it shares event handler logic with SearchInput.

During unit testing of this component, we will test the handleSearchChange in isolation and leave out checking communication between App and SearchInput for integration tests.

What is going on in lifecycle methods?


// App.js
componentDidMount() {
 doAsyncCall().then(() => {
   this.setState({ filteredEmoji: filterEmoji("", 20) });
 });
}

// doAsyncCall.js
function doAsyncCall() {
 return fetch("https://jsonplaceholder.typicode.com/todos/1");
}


export default doAsyncCall;

You will often initialize your state with an API call in the componentDidMount method, so I feel responsible to show you how to handle this even if this particular app loads data from a local JSON file.

I’ve moved the initialization of state.filteredEmoji to then() callback of promise returned by doAsyncCall – a method that make a request to JSONPlaceHolder API.

Summary

So, now we have a pretty clear overview of what we should test.

We’re ready to write actual unit test code that will reflect contract defined above.

In the last, third part of these series, we will finally make use of Jest and Enzyme.

Marcin Czarkowski

Marcin Czarkowski

There are no comments.

View Comments (0) ...
Navigation