Getting Started with Redux and Testing Library
Tags: redux, testinglibrary, testing, react
Published: 2022-06-02 22:26:11

<figcaption>Photo by <a href="https://unsplash.com/@saltsup?utm_source=medium&amp;utm_medium=referral" data-href="https://unsplash.com/@saltsup?utm_source=medium&amp;utm_medium=referral" class="markup--anchor markup--figure-anchor" rel="photo-creator noopener" target="_blank">Piret Ilver</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" data-href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" class="markup--anchor markup--figure-anchor" rel="photo-source noopener" target="_blank">Unsplash</a></figcaption>

---
If you’re reading this, I’m guessing you’re testing a [Redux](https://redux.js.org/) app with [Testing Library](https://testing-library.com/). And you probably want some tests to start with the Redux store in a particular state as the initial testing conditions.

As you probably know, Testing Library [emphasizes “testing behavior”](https://testing-library.com/docs/guiding-principles) (tests that interact with your app the way users would). Behavioral testing purists would say: to set up a Redux store with certain values, start the test by running through user interactions that populate the state.

However, that’s simply not practical to do for every test, especially if the desired state needs a lot of interactions (and possibly server values) for setup. This blog post details how to set up a **store factory** to generate a test store (with initial values) for test setup.

## Creating a Store Factory

The idea here is, you have a “factory function” to create a new store. This function creates the store for both production **and** tests  to make sure your tests are as close as possible to production code.

### Examples

Here’s an example of a store factory function using [Redux Toolkit](https://redux-toolkit.js.org/) and [Redux Saga](https://redux-saga.js.org/):

```js
import {
  Action,
  configureStore,
  EnhancedStore,
  ThunkAction,
} from "@reduxjs/toolkit";
import createSagaMiddleware from "redux-saga";

export const createStoreWithMiddlewares = (
  initialState = {}
): EnhancedStore => {
  const sagaMiddleware = createSagaMiddleware();

  const store = configureStore({ YOUR REDUCERS HERE },
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware().prepend(sagaMiddleware).concat(YOUR MIDDLEWARES HERE),
    preloadedState: initialState,
  });

  sagaMiddleware.run(YOUR ROOT SAGA);

  return store;
};
```

Here’s another one using [Redux](https://redux.js.org/) and [Redux Thunk](https://github.com/reduxjs/redux-thunk):

```js
import { createStore, applyMiddleware, Store } from "redux";
import ReduxThunk from 'redux-thunk';

export const middlewares = [ReduxThunk];

export const createStoreWithMiddlewares = (initialState = {}): Store => {
  return createStore(
    YOUR REDUCERS HERE,
    initialState,
    applyMiddleware(...middlewares)
  );
};
```

Both of these store factories have a `createStoreWithMiddlewares` function that takes an `initialState` and can be used to create either the production store or a test store — with the same configuration. You can use these examples to write a `createStoreWithMiddlewares` for your app.

### Using the Store Factory: Production

The production store can be created using `createStoreWithMiddlewares` either in your *store/index.js* file or *src/index.js*, and added as the `store` prop to the [Redux Provider](https://redux.js.org/tutorials/fundamentals/part-5-ui-react#passing-the-store-with-provider).

It’s **very important** to add the Redux Provider in *src/index.js* and **not** in *App.js*! If *App.js* contains the Redux Provider with the production store, then you won’t be able to test the `App` component with your test store, since the actual production store will be used when you render `<App />`.

## Using the Store Factory: Tests

Now that the production Redux Provider has been relegated to *index.js*, we have total control over the store for our tests. Follow these steps and delight in the power!

### Step 1: Create a Custom Render Function

We can overwrite [the Testing Library](https://testing-library.com/docs/react-testing-library/api#render) `[render](https://testing-library.com/docs/react-testing-library/api#render)` [function](https://testing-library.com/docs/react-testing-library/api#render) with [a custom render](https://testing-library.com/docs/react-testing-library/setup#custom-render) that includes a Redux Provider with a private store just for that test. Write this code in, say, *src/test-utils/index.tsx* (actual file location and name are not important. Also, if you’re not using Typescript, you’ll probably want to use *index.jsx* instead of *index.tsx*).

```js
import { EnhancedStore } from "@reduxjs/toolkit"; // for redux-toolkit
// import { Store } from 'redux' // for non-toolkit
import {
  render as rtlRender,
  RenderOptions,
  RenderResult,
} from "@testing-library/react";
import { ReactElement, ReactNode } from "react";
import { Provider } from "react-redux";

import { configureStoreWithMiddlewares, RootState } from "../store";

type ReduxRenderOptions = {
  preloadedState?: RootState;
  store?: EnhancedStore; // for redux-toolkit
  // store?: Store // for non-toolkit
  renderOptions?: Omit<RenderOptions, "wrapper">;
};

function render(
  ui: ReactElement,
  {
    preloadedState = {},
    store = configureStoreWithMiddlewares(preloadedState),
    ...renderOptions
  }: ReduxRenderOptions = {}
): RenderResult {
  function Wrapper({ children }: { children?: ReactNode }): ReactElement {
    return <Provider store={store}>{children}</Provider>;
  }
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}

// re-export everything
export * from "@testing-library/react";

// override render method
export { render };
```

(this code is adapted from the [Redux Testing Docs](https://redux.js.org/usage/writing-tests#components)). Note that the Typescript is different for Redux-Toolkit than for plain Redux; use the lines that apply to your project (or no Typescript at all if that’s your jam).

The idea with the above code:

*   The custom render in this doc takes a `preloadedState` and UI component.
*   The custom render wraps the UI component in a Redux Provider, with a store that contains the `preloadedState`.
*   The code exports everything from *@testing-library/react* and then overrides the `render` method, so this file can be used **in place of** the actual *@testing-library/react* module (as we’ll see when we use it).
*   When importing from this file instead of *@testing-library/react,* all the methods except `render` (such as `screen` or `fireEvent`) will come straight from *@testing-library/react* — except `render`, which has been replaced with our custom `render`.

Note that you can create a store beforehand and pass it to the `render` function, or you can use the default, which is creating a new store with your `preloadedState`, using all of the configuration from the `configureStoreWithMiddlewares` function that our production uses:

```js
    store = configureStoreWithMiddlewares(preloadedState),
```

If you do create a store and pass it as an argument, it’s **very important that a new store is created for every test** (so that there’s no sharing of state between tests).

### Step 2: Using custom render in tests

To use this custom render in a test, we’ll import from our *test-utils/index.tsx* file instead of from *@testing-library/react*.

Say you have a user profile page, that looks like this:

![user profile web page showing user name and email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tdqswsl0sz537796pxc.png)

<figcaption>our web site has won numerous awards for our clean, spare style&nbsp;😝</figcaption>

The `UserProfile` component might look something like this:

```js
import { EnhancedStore } from "@reduxjs/toolkit"; // for redux-toolkit
// import { Store } from 'redux' // for non-toolkit
import {
  render as rtlRender,
  RenderOptions,
  RenderResult,
} from "@testing-library/react";
import { ReactElement, ReactNode } from "react";
import { Provider } from "react-redux";

import { configureStoreWithMiddlewares, RootState } from "../store";

type ReduxRenderOptions = {
  preloadedState?: RootState;
  store?: EnhancedStore; // for redux-toolkit
  // store?: Store // for non-toolkit
  renderOptions?: Omit<RenderOptions, "wrapper">;
};

function render(
  ui: ReactElement,
  {
    preloadedState = {},
    store = configureStoreWithMiddlewares(preloadedState),
    ...renderOptions
  }: ReduxRenderOptions = {}
): RenderResult {
  function Wrapper({ children }: { children?: ReactNode }): ReactElement {
    return <Provider store={store}>{children}</Provider>;
  }
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}

// re-export everything
export * from "@testing-library/react";

// override render method
export { render };
```

You can see that the `user` piece of state has `name` and `email` properties. To test that the user `name` and `email` show on the profile page, you need to preload the state with a user object for the test.

Here’s how the tests might look with our custom `render` method:

```js
import { render, screen } from "../../test-utils"; // adjust for relative path to *your* test-utils directory 
import { UserProfile } from "./UserProfile";

const fakeUser = {
  name: "Tess Q. User",
  email: "tess-user@fake.domain.com",
};

test("User profile shows name and email", () => {
  render(<UserProfile />, { preloadedState: { user: fakeUser } });

  expect(screen.getByText("Tess Q. User")).toBeInTheDocument();
  expect(screen.getByText("tess-user@fake.domain.com")).toBeInTheDocument();
});
```

Here are the steps in the custom `render` method that make this work:

1.  The custom `render` method uses the `preloadedState` option (and the `createStoreWithMiddlewares` function used in production) to create a new store.
2.  The custom `render` method then creates a wrapper with a Redux Provider, passing the store with the preloaded state as a prop.
3.  The custom `render` method uses the *actual* testing-library/react `render` to render the `ui` argument (in this case, `<UserProfile />`) wrapped in the newly-created Provider from step 2, and returns the result.

This result now has the store pre-populated with the specified user, the `useSelector` call in the component returns the `fakeUser`, and the tests pass.