Advanced Testing Setup for an Enterprise React Project
Tags: react, javascript, aws, programming
Published: 2024-01-30 08:59:49

To read more articles like this, [visit my blog](https://www.mdfaisal.com/blog)


Testing is an essential aspect of any severe application. Testing is not only to improve coverage; the primary purpose is to test real-world usage as closely as possible.

Recently, I had to set up the testing architecture for a large ReactJS project.

Let me show you how I did it. It’s incomplete, but I wanted to share the progress with you.

## Why?

The project was already using **Enzyme** for testing.

It’s a good library but `react-testing-library` is a better alternative for testing react components. The React team also recommends it.

[**Testing Overview - React**](https://legacy.reactjs.org/docs/testing.html)

As many engineers are working on the same project on different parts, It was imperative to establish a common framework for common use cases.

## The Scenarios

Testing is a very, very important part of any good React application. And **react-testing-library** is the recommended way of testing any modern React application.

Although react-testing-library makes it really easy and intuitive to test components based on their behavior, sometimes setting up a component for testing can get tricky.

Let’s see how we can solve different issues.

## Scenario 1: Testing Redux Connected Components

Testing pure components that are only controlled by props is easy to test. But, more often than not, that’s not the case.

If your component depends on the redux state, you can’t test all behaviors without connecting to the redux state.

> So what can we do?

First, we need to create a reusable function to render a component. It’s kinda similar to the **render prop pattern** in ReactJS.

It will take a **store** and an **initialState** as an argument. These are the values of the redux store that you want to test your component with.

```js
import { render, RenderOptions } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import {initialState as reducerInitialState , reducer, store} from 'reducer';

type RenderConnectedInterface = {
  initialState: Partial<typeof reducerInitialState>;
  store?: typeof store;
} & RenderOptions;

const renderConnected = (
  ui: React.ReactElement,
  {
    initialState = reducerInitialState,
    store = createStore(reducers, initialState),
    ...renderOptions
  }: RenderConnectedInterface = {} as RenderConnectedInterface
) => {
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
    <Provider store={store}>
      {children}
    </Provider>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

export default renderConnected;

```

Basically, we are taking the **store** and an **initialState** as a parameter of the function.

Then we wrap the passed component with the Redux **Provider** .

Now instead of using the default **render** method provided by **react-testing-library** , you can test your component with **renderConnected** function and pass the piece of the store that you want.

```js
import {  screen } from '@testing-library/react';
import React from 'react';
import SomeComponent from './SomeComponent.tsx';
import renderConnected from 'utils/renderConnected';

describe('Test SomeComponent', () => {
  const initialState = {
    name : "your name"
  };

  it('should show the name properly', () => {
    const props = {
      // ... pass any additional prop
    };
    renderConnected(<SomeComponent {...props} />, { initialState });
    expect(screen.getByText("your name")).toBeDefined();
  });
});
```

Awesome right?

## Scenario 2: Using UI Library and Customized Theme

But the problem doesn’t end there. Many times we need to wrap our root component with many types of providers.

One example is the **ThemeProvider** of **material-ui** or **styled-components** .

```
<ThemeProvider theme={theme}>
  <CustomCheckbox defaultChecked />
</ThemeProvider>
```

Now if you want to test a component’s functionality that uses the values passed by these Providers, your tests will fail!

We can use the same concept to alleviate this issue and wrap our root component with the theme provider.

So to alleviate that, let’s adjust the **renderConnected** function to wrap the components with **ThemeProvider**.

```js
import { render, RenderOptions } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import {initialState as reducerInitialState , reducer, store} from 'reducer';

// ---- NOTICE HERE
import { ThemeProvider } from 'styled-components';
import {customTheme} from './customTheme'

type RenderConnectedInterface = {
  initialState: Partial<typeof reducerInitialState>;
  store?: typeof store;
} & RenderOptions;

const renderConnected = (
  ui: React.ReactElement,
  {
    initialState = reducerInitialState,
    store = createStore(reducers, initialState),
    ...renderOptions
  }: RenderConnectedInterface = {} as RenderConnectedInterface
) => {
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
    <ThemeProvider theme={customTheme}> // .................... NOTICE HERE......................... <-
      <Provider store={store}>
        {children}
      </Provider>
    </ThemeProvider>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

export default renderConnected;

```

Now we can pass any component, and our tests will pass.

## Scenario 3: Testing with React Router

It’s a really common practice to navigate to a new route once any action is completed.

Let’s say you want your users to be re-directed to the home page after the login is successful.

> How can we do that?

We can take advantage of the **MemoryRouter** provided by **react-router** We can pass down the URL path and test our component.

We will see how it works later, but let’s add that to the code first!

The modified version of the **renderConnected** will look like this

```js
// .. same as before
import { MemoryRouter } from 'react-router-dom';


type RenderConnectedInterface = {
  initialState: Partial<typeof reducerInitialState>;
  store?: typeof store;
  
  route?: string; // new one!
} & RenderOptions;

const renderConnected = (
  ui: React.ReactElement,
  {
    initialState = reducerInitialState,
    store = createStore(reducers, initialState),
    route = '/', // new addition
    ...renderOptions
  }: RenderConnectedInterface = {} as RenderConnectedInterface
) => {
  const Wrapper = ({ children }: { children: React.ReactNode }) => (
    <ThemeProvider theme={customTheme}>
      <Provider store={store}>
        <MemoryRouter initialEntries={[route]}>{children}</MemoryRouter> // new addition!
      </Provider>
    </ThemeProvider>
  );
  return render(ui, { wrapper: Wrapper, ...renderOptions });
};

export default renderConnected;
```

Notice we are passing a new parameter **route** into the function now. We are also wrapping our **children** with the **MemoryRouter** provided by the **react-router**.

## Test the navigation

Let’s say you are testing a **FirstPage** that, on the click of a button, navigates to another **SecondPage** . And you want to test this behavior.

But the problem is that **SecondComponent** is not mounted yet…. right?

One way to do that is to mock **useNavigation** or use the **history** object of **react-router**.

But there is a simpler way. We will use **Router** from **react-router-dom** mount a dummy component for the second URL path and ensure that it comes into the picture.

```js
it('Test navigation' , () => {
  const ui = renderConnected(
    <>
      <FirstPage />
      <Route path="/second-page">Second Page</Route>
    </>,
    { initialState }
  );
  
  expect(screen.queryByText('Second Page')).toBeNull();
  expect(screen.getByText(/First Page/)).toBeDefined();
  
  // do the action
  const button = utils.getByText(/Submit/);
  fireEvent.click(button);
  
  // the navigation should occur at this point and we should be on the second page
  
  expect(screen.getByText(/Second Page/)).toBeDefined();
  expect(screen.queryByText(/First Page/)).toBeNull();

})
```

Now, your tests will pass, and you will be greeted with those sweet green checkboxes!

![test passed](https://cdn-images-1.medium.com/max/2476/1*B1ae5zsgoLk8Pg99eoyZGg.png)

## What to do next?

These are the three scenarios that I have had to encounter so far. But you get the basic idea.

Whenever you face a new scenario or need to integrate some other library, think of how to add that to our magical **renderConnected** function.

And you and your team will be happy!

## Conclusion

I have shown you three of the most common use cases of how we can leverage the concept of `composition` to solve these testing scenarios in ReactJS.

I hope you learned something new today! Have a wonderful Day! :D

**Have something to say? Get in touch with me via [LinkedIn](https://www.linkedin.com/in/56faisal/) or [Personal Website](https://www.mdfaisal.com/)**