The times when a front-end was just a thin layer of static pages are long gone. Modern web applications grow ever more complex, and logic continues to shift from the back-end to the front-end. Yet when it comes to testing, many keep the same, outdated mentality. If you work with React and Redux, but for some reason aren’t interested in testing your code, I’m here to show you how and why we do it on a daily basis.

Note: I’m going to be using Jest and Enzyme. It’s the most popular stack for testing React and Redux applications. I assume you’ve had a try or are familiar with them to some extent.

A word on unit tests vs integration tests

React and Redux applications are built upon three basic building blocks: actions, reducers and components. The choice of whether to test them independently (unit tests), or together (integration tests) is up to you. Integration tests cover the whole functionality, treating it like a black box, whereas unit tests focus on a specific building block. From my experience, integration tests fit very well in applications that tend to grow in size, but are rather simple. On the other hand, unit tests come in handy when you’re building non-trivial solutions with logic usually spread between building blocks. Although most systems fit the first group, I’ll start with unit-testing to better explain the application layers.

What we’ll build (and test)

The app is available here. When you first enter, no image is displayed. You can fetch one by clicking the button. I’m using free Dog API. Now, without further ado, let’s write some tests. The code is available here.

Unit tests: Action creators

In order to display a dog, we first need to fetch it. If you’re not familiar with thunk, don’t worry. Thunk is a middleware that allows us to return a function instead of a plain action object. We can use that to dispatch the success action or failure action, based on the HTTP request result.

We want to test if retrieving data from the API actually results in dispatching the success action along with the data. In order to do that, we’ll use redux-mock-store.

Note: I’m using axios for a http client, and axios-mock-adapter to stub calls to the actual API. You’re free to pick whatever suits you best.

For the setup, let’s initialize the mock store and stubbed http client in beforeEach(). In the test itself, we specify the response for a request. Later, we execute our actual action creator. Since we use thunk, it returns a function that accepts the store’s dispatch method. Before any assertion, the request needs to be resolved, therefore we flush all of the pending promises.

This line resolves every promise in a single event loop-tick. window.setImmediate is used to break up long-running operations and run a callback function immediately after the browser has completed other operations such as events and display updates. In this case, our hanging HTTP request is this operation we want to finish. Also, since it’s not a standard feature, you shouldn’t use it in production code.

Unit tests: Reducers

I like to think about reducers as the heart of the application. If you develop feature-rich, non-trivial systems, a big part of the complexity will end up here. If you introduce a bug, it’s likely to be hard to track down later. That’s why it’s so important to test reducers. The application we’re building is very simple, but I hope you get the picture.

Every reducer is invoked at the start of the app, hence the need for an initial state. Leaving your state undefined will make you write pesky, defensive checks in components.

It’s rather straightforward. We run the reducer with an undefined state and check whether it returns the state with initial values.

We must also ensure that the reducer properly reacts to a successful fetch and assigns an image URL.

Reducers should be pure functions. No side-effects. That’s what makes testing them so easy. Provide a before state, an action to be dispatched, and verify whether an output is correct.

Unit tests: Components

Before we start, let’s talk about what aspects of components are worth testing. We obviously can’t test if the component looks pleasant. However, we should definitely test if certain conditional elements are in fact displayed or not; or whether performing some action on a component (not to be confused with Redux actions) results in the function passed in the component’s props being called.

In our system, we rely solely on the Redux as the application state, hence all our components are stateless.

Note: If you’re looking for classy Enzyme assertions, go check out enzyme-matchers.

The component structure is simple. We have DogApp root components and RandomDog components for fetching and displaying dog images. RandomDog props look like this:

Enzymes allow us to render a component in two ways. Shallow rendering means that only a root will be rendered. If you print the text of a shallow rendered component, you can see that all below components are not rendered. Shallow rendering is perfect for testing components in isolation, and since Enzyme 3 onwards (and optionally Enzyme 2), it invokes lifecycle methods, such as componentDidMount(). We will cover the second way later.

Now let’s write test cases for the RandomDog component.

First, we want to ensure that the placeholder is displayed when no image URL is provided. Also, no image should be displayed.

Second, when provided with the dogUrl, the image should be displayed in place of the placeholder.

Finally, clicking the fetch dog button should result in executing the fetchDog() function.

Note: In this example, I’m using element and class selectors. If you find it fragile and refactor your code a lot, consider switching to custom attributes.

2 unit tests, 0 integration tests

I’ll explain the problem with unit tests with this contemporary cliché meme.

Although unit tests are a great tool, they don’t presume that we properly connect our components, or that a reducer is subscribed to the right action. It’s a common place for bugs, and that’s why we need integration tests.

And yeah, there are people who claim that because of the above, unit tests are useless, but I think they haven’t faced a system complicated enough to acknowledge their value.

Integration tests

Instead of testing building blocks separately and in detail, we’ll now bundle them together and put them in a black box. We don’t care anymore how the stuff inside works. What happens in the component, stays in the component. That’s why integration tests are so resilient and refactor-proof. You can switch the entire underlying mechanism without the need to update the test.

In integration tests, we don’t want to use the mock store anymore. Let’s bring a real one.

That’s it. Now, that we have a fully functional store, it’s time to move on to the first test. We’ll use Enzyme’s mount type of render for that purpose. Mount is a perfect fit for integration tests, since it renders the whole underlying component tree.

As we did in unit tests, we want to check if on application startup there is no image displayed. But now I’m not passing the empty image URL as a component’s prop, but rather wrapping it in Provider, passing the store we created.

Nothing fancy, huh? Let’s look at the second test case.

See how easy this is? The test is very descriptive and we perform real interactions with our component. It covers every aspect unit tests did, and even more. Now we can actually tell if building blocks not only work well separately, but are coupled together in the right way.

Oh, if you’re familiar with Enzyme and wondered why I had to call wrapper.update(), here’s why. TLDR: It’s a bug in Enzyme 3. Perhaps by the time you’re reading this, it will be fixed.

A word on snapshot testing

Jest offers a way to ensure code changes won’t alter the output of a component’s render() method. Although writing snapshot tests is quick and easy, they aren’t descriptive and you cannot test-drive your development process. The only use case I see is when you’re about to make some changes in your friend’s untested legacy code that you don’t care about enough to clean up, but don’t want to get blamed for breaking it at the same time.

So what type of tests shall I use?

Simply start with integration tests. It’s very likely that you won’t feel the urge to implement a single unit test in your project. That means your complexity isn’t divided between building blocks, and it’s perfectly fine. You’ll save quite some time. On the other hand, there are systems that will leverage the power of unit tests. There is room for both.