DEV Community

Jagoda Bieniek
Jagoda Bieniek

Posted on

Getting Started with Redux in React Testing Library: A Guide to Custom Render Functions.

Introduction

Often in our projects we need to manage the state. For this purpose, we can use Redux however in 2024 there is a Redux toolkit , which simplifies the use of Redux itself. In this tutorial, I want to focus on how to start testing an application that uses the Redux toolkit.

Custom render function.

In testing React components, It is often used external libraries, not only redux-toolkit, but also react-router-dom or styled-components . In these cases, it is usefull to create custom render function.

Let's take as example boardSlice.ts (example from my project):

Image description

Import the necessary modules and components from React, Redux Toolkit, React Testing Library, and your application.

import { ReactElement, ReactNode } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { boardSlice } from './store/slices';
Enter fullscreen mode Exit fullscreen mode

Define the CustomRenderOptions interface witch extends the RenderOptions from @testing-library/react, excluding the wrapper property. It adds optional preloadedState and store properties. This allows you to pass an initial state for the Redux store or a custom store when calling customRender.

interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
  preloadedState?: Record<string, unknown>;
  store?: ReturnType<typeof configureStore>;
}
Enter fullscreen mode Exit fullscreen mode

Create the customRender function takes a React element and an options object as arguments. The options object defaults to an empty object if not provided.
Create the Redux store: If a store is not provided in the options, a new store is created using the configureStore function from Redux Toolkit. The store uses the boardSlice.reducer as its reducer and the preloadedState as its initial state.

const customRender = (
  ui: ReactElement,
  {
    preloadedState = {},
    store = configureStore({
      reducer: { board: boardSlice.reducer },
      preloadedState,
    }),
    ...options
  }: CustomRenderOptions = {}
) => {
  // ...
};
Enter fullscreen mode Exit fullscreen mode

Create the Wrapper component, wraps its children in a Redux Provider. The Provider makes the Redux store available to the components.

const Wrapper = ({ children }: { children: ReactNode }) => (
  <Provider store={store}>{children}</Provider>
);
Enter fullscreen mode Exit fullscreen mode

Call the render function finally, the function calls the render function from React Testing Library, passing the ui and the Wrapper as the wrapper option.

return render(ui, { wrapper: Wrapper, ...options });
Enter fullscreen mode Exit fullscreen mode

Export the customRender function, then exported as render, so it can be used in place of the render function from @testing-library/react.

export { customRender as render };
Enter fullscreen mode Exit fullscreen mode

Finally we receive :

import { ReactElement, ReactNode } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { boardSlice } from './store/slices';
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
  preloadedState?: Record<string, unknown>;
  store?: ReturnType<typeof configureStore>;
}

const customRender = (
  ui: ReactElement,
  {
    preloadedState = {},
    store = configureStore({
      reducer: { board: boardSlice.reducer },
      preloadedState,
    }),
    ...options
  }: CustomRenderOptions = {}
) => {
  const Wrapper = ({ children }: { children: ReactNode }) => (
   // Here you can other providers
    <Provider store={store}>
      {children}
    </Provider>
  );

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

export * from '@testing-library/react';
export { customRender as render };

Enter fullscreen mode Exit fullscreen mode

Sumarry

In this tutorial, we've explored how to create a custom render function for testing React components that interact with a Redux store, using Redux Toolkit. This custom render function simplifies the testing process by automatically wrapping the component under test in the necessary context providers, such as Redux's Provider and React Router's MemoryRouter.

We started by importing the necessary modules and defining a CustomRenderOptions interface to extend the RenderOptions from @testing-library/react. This interface allows us to optionally pass an initial state or a custom store to the customRender function.

Next, we created the customRender function, which takes a React element and an options object as arguments. Inside this function, we created a Redux store (if not provided in the options) and a Wrapper component that wraps its children in a Redux Provider.

Finally, we called the render function from @testing-library/react, passing the ui and the Wrapper as the wrapper option, and exported the customRender function as render.

This custom render function is a powerful tool that can simplify your test setup, reduce repetition in your tests, and make your tests more robust and flexible when working with libraries like Redux Toolkit.
With this configuration, your application is now equipped to handle Redux Toolkit in unit tests. In subsequent sections, we will delve into the specifics of what to test and how to conduct these tests within the Redux Toolkit context.

Top comments (0)