Guide: Testing Redux Connected Components with React Testing Library and Jest

thumbnail for Guide: Testing Redux Connected Components with React Testing Library and Jest

In my experience unit testing React components was always pretty simple until you needed to integrate with an external library like Redux. There are countless "solutions" available online, but I wanted to present you with my simple, yet powerful solution. I used it in a few commercial projects with great results.

It couldn't be easier without React Testing Library, which revolutionized testing components in React.

A common mistake with testing Redux connected components

One solution which was, for some reason, quite popular was testing components connected to Redux without the real connection. Here is an example of what I mean by that.

import React from "react";
import { connect } from "react-redux";

const MyComponent = () => { ... };

const mapStateToProps = state => ({
	data: state.someData,
});

export { MyComponent as MyComponentUnwrapped };
export default connect(mapStateToProps)(MyComponent);

And then in tests, you would import MyComponentUnwrapped instead of your default export used everywhere else in the app.

In this case, you are not testing an important part of your component. Other than that MyComponentUnwrapped is used only by your tests, your real application uses the default export. You can get your tests passing where in reality the same cases could fail.

How to test components in React Testing Library

React Testing Library provides a very intuitive API. Its main goal is to test components the same way user will use them in your application. Of course, the same is possible with other testing libraries like Enzyme, but React Testing Library is very strict about it and doesn't allow to access the internals of your component.

Enough with theory. Let's write some tests!

Let's say we have some components, which fetch user data and display it.

import React, { useState, useEffect } from "react";

import { getUserData } from "./api";

const User = () => {
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    getUserData.then((data) => {
      setUserData(data);
    });
  });

  if (!userData) {
    return null;
  }

  return <div>{userData.name}</div>;
};

export default User;

Quite simple component, Now let's see how it can be tested

import React from "react";
import { screen, render } from "@testing-library/react";
import User from "./User";

jest.mock("./api", () => ({
  getUserData: () => ({ name: "mock name" })
}));

describe("User", () => {
  it("should display user name", async () => {
    render(<User />);

    const userName = await screen.findByText("mock name");

    expect(userName).toBeTruthy();
  });
});

The first thing we have to do is mock our API call with jest.mock. Normally it would make a network request, but in tests, we need to mock it.

Then we use render function to render our component, and screen.findByText to search for text in the component we just rendered.

Testing Redux connected components

Now let's suppose we would need to access user data in other parts of the application. Let's move it to the Redux store. Here is how the refactored version of the component might look like.

import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { fetchUserData } from './actions';

const User = ({ userData, fetchUserData }) => {
  useEffect(() => {
    fetchUserData();
  }, []);

  if (!userData) {
    return null;
  }

  return <div>{userData.name}</div>;
};

const mapStateToProps = (state) => ({
  userData: state.user
});

const mapDispatchToProps = {
  fetchUserData,
};

export default connect(mapStateToProps, mapDispatchToProps)(User);

Now the first thing you will notice in your test is: Could not find "store" in the context of "Connect(User)" error. It's because your component needs to be wrapper in Provider to access Redux Store. Let's fix our tests:

import React from "react";
import { screen, render } from "@testing-library/react";
import { createStore } from "redux";
import User from "./User";
import reducer from "./reducer";
import store from "./store";

jest.mock("./api", () => ({
  getUserData: () => ({ name: "mock name" })
}));

const initialState = {
	user: { name: "mock name" },
};

const store = createStore(reducer, initialState);

const Wrapper = ({ children }) => (
	<Provider store={store}>{children}</Provider>
);

describe("User", () => {
  it("should display user name", async () => {
    render(<User />, { wrapper: Wrapper });

    const userName = await screen.findByText("mock name");

    expect(userName).toBeTruthy();
  });
});

We fixed the error by creating a Wrapper. This component will wrap the component we test with Provider and apply a mocked state. We can go one step further by custom render function using the one from React Testing Library.

import React from "react";
import { render as rtlRender } from "@testing-library/react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import reducer from "./reducer";

export const renderWithState = (
  ui,
  { initialState, ...renderOptions } = {}
) => {
  const store = createStore(reducer, initialState);
  const Wrapper = ({ children }) => (
    <Provider store={store}>{children}</Provider>
  );

  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

And then in our case we can just import it and use like this:

renderWithState(<User />, { initialState });

Testing components with Redux hooks

The approach presented above is also compatible when using React Redux hooks and selectors, as long as they use the data we provide them in the state.

This is the true advantage of the React Testing Library. No matter what you use for connecting your component to Redux. It only tests what your component renders, without diving deep into implementation details.

You might also like

thumbnail for Guide: Unit Testing React Apollo Components with React Testing Library
November 23, 2020
Guide: Unit Testing React Apollo Components with React Testing Library
In this post I will present you a quick guide how to test React components which use Apollo.
thumbnail for 7 Tips to Get Up to Speed Quickly in New Job or Project
November 9, 2020
7 Tips to Get Up to Speed Quickly in New Job or Project
Changing projects often taught me how to onboard quickly to new project and contribute as soon as possible.
thumbnail for Tailwind CSS: The future of styling or just another CSS framework?
January 15, 2021
Tailwind CSS: The future of styling or just another CSS framework?
Tailwind CSS looked like the first framework I would enjoy using. I already saw how easily you can create beautiful UIs with it (thanks to Tailwind UI project).

Hi friend,

Over the years working as a Front-end Developer in numerous projects I have gained a lot of experience. I've seen how choice of the right technology and architecture can affect the application and team behind it in a long run.

On this blog I want to share my experiance. I will show you solutions I discovered over the years. I want to teach you how to create better code and be successful developer.

Join the list of developers interested in web development

After signing up you will receive for free: Interview questions I ask as technical recruiter

newsletter