1. Functional App

Click here to go to the repo and branch. If you’d like to go straight into testing, please go to section 2 of this blog post.

What the app looks like

As you can see the app is simple. It has a header, an input field which takes the query and based on that calls to the Guardian API and returns the results of related articles in a list format.

This is the directory format:

Folder structure

As we’re only using React state, I’ve separated my components into components and containers. Components take care of presentational code and containers do the logic side of things such as API calls. I won’t go too much into detail about the set-up as this can be found in my previous post.

Note: I have added extra settings/packages to this project so if anything is unclear, you can ask me in the comments :)

path: src/components/Search/index.js

The Search component’s job is to show the app’s interface to the user. This component has an internal state of value , and two class methods handleChange and handleSubmit . The component renders the form and I bind the handleSubmit method to this form. This method ensures that we do not serve the default action of the browser upon form submission (which is submission to the server). In the input field, I bind the value state to value attribute so that the component has a memory of what the user has typed. I also bind handleChange method to onChange attribute to then take user’s input and pass it to the performSearch method. The reason why it comes through props is because this method comes from the Search container method. Also you can also write the setState method for the value like this:

this.setState({ value: event.target.value }) or let value = event.target.value

this.setState({ value: value }) With ES6, if the key-value pairs are the same, you can collapse it to just one word as I did previously: let value = event.target.value

this.setState({ value })

I then have a SearchResults component which I pass in the articles props that come from the Search container which I will show you in a bit. But first let’s take a look at the SearchResults.

path: src/components/Search/SearchResults/index.js

The articles props get pass in and we map through all the results in articles.

API results in JSON format:

The Guardian API results for ‘cake’ search term

path: src/containers/Search/index.js

You can see that this SearchContainer has an internal state of articles array. It also has a performSearch class method which takes an event argument to pass it to a method called fetchArticles. I have extracted this method into the api folder:

You can see that this SearchContainer has an internal state of articles array. It also has a performSearch class method which takes a query string argument to pass it to a method called fetchArticles. I have extracted this method into the api folder:

path: src/api/index.js

The reason why I extracted this method, will become apparent when we come to testing it.

So we get the response from the API which is all done in fetchArticles method, the body text is then parsed to JSON using the json() method. Going back to the SearchContainer, we then get that response, call it data, and setState of that data.response.results to articles internal state.

If I hadn’t extracted the method, the performSearch method would look like this:

Hope this diagram makes it easier to see how this works? Apologies for the mega ugly and low-res photo but I was using a free online photo editor.

Once the results are in, it is passed to the articles state, which is then passed to the Search component!

This is why we have to call .props on these performSearch method and articles state in the Search component:

App Component

To tie all of these components, we also need an entry app component in which we’re importing the Search Container.

Hoping that makes sense! Do comment down below if you’re unclear about anything. Without further ado, let’s get onto testing!

2. Unit Testing ✅

Click here to go to repo and unit testing branch.

First we need to install some packages which we’ll be using to test our React components:

npm i -D enzyme enzyme-adapter-react-16 jest

You might be wondering why we need to install both Enzyme and Jest? Enzyme provides the testing utility functions for React components such as shallow, mount and render whereas Jest is a test runner and an assertion library. Without Jest, there is no way to run Enzyme tests.

Enzyme adapter is required to provide compatibility to different React versions. As we are currently using version 16+ we need to install enzyme-adapter-react-16.

🔎 Search Component Unit Tests

Let’s start with the Search component. You need to create a Search.test.js file in the Search folder. Then we need to import the stuff we need in order to test and configure our Enzyme adapter.

React : always need to import React

: always need to import React Enzyme, { shallow, mount } : these are the Enzyme method’s we’ll be using in our tests

: these are the Enzyme method’s we’ll be using in our tests Search : we need to import the component we’re testing

: we need to import the component we’re testing Adapter: to configure the Enzyme adapter

First test!

Here we are testing whether the Search component renders. We pass <Search /> component to shallow() method from Enzyme and store is in a wrapper variable. We then check that this component exists using a boolean assertion. Pretty simple right?

To run your test, navigate to package.json and add the following script:

"scripts": {

..., "test": "jest --verbose",

...

}

You can run your tests using the following command:

npm test

The --verbose flag will allow you to display individual test results with test suite hierarchy.

Notice the describe block comes first then comes the indented test suite below it.

Two other testing scripts I’ve added are:

"scripts": {

...,

"test": "jest --verbose",

"test:watch": "jest --watchAll --verbose",

"test:coverage": "jest --verbose --coverage",

...

}

test:watch will run everytime there is a change in the test and/or component file

will run everytime there is a change in the test and/or component file test:coverage will show the percentage of code we have covered in our tests

Note: to run these in your terminal you need to state npm run test:watch or npm run test:coverage

This step is optional. Feel free to play around with it but it’s not necessary for this.

Great let’s go onto the next test!

The next thing we’re testing is whether the user text input was echoed. This time we have to pass in the performSearch method as a prop to the Search component and give it an empty function. We have to do this because the component expects its parent i.e. SearchContainer to pass a callback.

We then find the input node and use simulate API to simulate a change event with the target being the value state with a string “hello”.

We then do our assertion where we find this input node again and look at its value props and check whether it equals to “hello”.

Next test, we want to check that when the form is submitted, the event is cancelled meaning the default browser submit function isn’t fired.

Here we make a prevented variable to equal to false. We then find a form from the Search component, simulate a submit event in which a preventDefault method gets called and changed the prevented variable to true. We then check that this has been changed by running an assertion on it.

🔎 SearchResults Component Unit Tests

The same way as before, we have to import the things we need and configure the adapter for Enzyme.

For the first test, we’re doing the same thing as before where we check that SearchResults renders. However this time around, I decided to do it using Snapshot testing to show how it works. When you first run this test, it will save the rendered DOM tree in a __snapshots__ folder.

Everytime the test is run, it will then compare the current DOM to the existing one taken in the __snapshots__ folder.

Here we are also mocking some data to pass through to the articles prop in the SearchResults component. We then check whether the SearchResults component renders the same DOM tree as the one created in __snapshots__ folder using toMatchSnapShot() method.

The next test I am testing the DOM tree when no data is being passed through.

The next test, I am using Enzyme again to check that the SearchResults component doesn’t break even without the articles props being passed in. We do this by checking that there are no <li> tags as this would have been rendered if articles had data in and being passed through to SearchResults component.

The last test for this component is to check that even with empty articles prop, the component doesn’t break.

3. Integration Testing ✅

Click here to go to repo and integration testing branch.

The last part of this blog post is to show you how I’ve done some integration testing in the Search and Search Container component.

Going back to the Search component, we’ll be adding one integration test:

Here we are checking whether Search component renders the search results when the articles state change.

This test is a bit different as we are using the mount method from Enzyme. Mount provides full rendering including any child components. Previously we had only used shallow which only checks and renders the component in question and doesn’t look at any child components.

Since SearchResults component is a child of Search component we need to check into it and make sure articles exists. We pass the Search component articles props as an empty array. We then setProps with the articles array to have a webUrl and webTitle as this is what we map through in the SearchResults component. We then find the anchor tag with its href attribute to equal to the same webUrl when we set the props.

I previously included this test in the unit testing section. However I later asked myself the question whether using mount would make it an integration test. I then realised that if I’m using it to test a child component’s behaviour then it is integration test. It would only be a unit test if I’m only testing the parent component.

🔎 Search Container Integration Testing

As we’ll be mocking the API response we need to do some additional configurations. We’ll be using Jest’s manual mock function so the start of your test file should start like this:

path: src/containers/Search/Search.test.js

In my api folder I have an API call method:

path: src/api/index.js

I have also included a mocked version of this which we will later use for our test:

path: src/api/__mocks__/index.js

Here we are resembling the actual API response using Promise.resolve(). Comparing to the actual response, we have an an object called response, followed by results, and webUrl and webTitle which we use in Search Results component. This will make more sense when we come to the last test.

The first 2 tests are pretty simple. I will skip over the first test as it’s technically a unit test, similar to some we have done previously. The second test we’re using mount again to check that SearchContainer component has Search component as a child.

The last test consists of using the mock! Here we’re setting the initial articles state to be an empty array. We then Search for the performSearch props from the Search component.

Note that this line:

const { performSearch } = wrapper.find(Search).props()

can also be written as:

const performSearch = wrapper.find(Search).props('performSearch')

However I am using ES6 destructuring assignment here so I don’t have to repeat performSearch.

peformSearch is then invoked to return a promise in which we then are expected the articles state length to be 10 (as this is what we have mocked previously in our API).

Now when you run your npm run test:coverage you should get these results:

That concludes my attempt at explaining how to test React components. Thanks a lot for taking the time to read it! Please do leave any improvements and suggestions in the comments as I am still learning a lot about React testing and want to get better at it!

Special thanks to Minh Nguyen for helping me with the tests, Oliver Turner and Tomasz Wegrzanowski for proof-reading and suggestions!