React Testing Library Configuration for Productive Unit Testing
Tags: react, testing, graphql, javascript
Published: 2023-09-02 16:51:53

---
title: React Testing Library Configuration for Productive Unit Testing
published: true
date: 2022-07-11 17:52:01 UTC
tags: react, testing, graphql, javascript
canonical_url: https://morintd.hashnode.dev/react-testing-library-configuration-for-productive-unit-testing-5d0c446f3b3d
---

![Cover](https://cdn.hashnode.com/res/hashnode/image/upload/v1692035720359/89ca42b4-bf6f-4b95-aeaa-39f73f87ddfc.jpeg?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp)


Too often, I join a new React project where unit tests are lacking, both in amount and quality. There are a few causes, but the one I want to discuss today is the poor test environment.

Indeed, testing requires skills, thoroughness, and is definitely time-consuming (even if thats worth it!). If testing is more painful than necessary, it becomes a signal to avoid writing tests altogether.

### Environment

With React, the tools I recommend are Jest and React Testing Library. Nothing fancy here; they are the de-facto standard in the community.

{% embed https://hashnode.com/post/cllfjb3v0000709m8920i8sw5 %}

### Example

To demonstrate how to write great tests, in a good environment, we need a component to test, right? Lets use a common functionality: a counter.

Ill start with the following component, inside its own `Counter.tsx` file.

```
import { useState } from 'react';

const Counter = () => {
  const [counter, setCounter] = useState(0);
  const decrement = () => setCounter((count) => count - 1);
  const increment = () => setCounter((count) => count + 1);

  return (
    <div>
      <h1>Counter: {counter}</h1>
      <button onClick={decrement}>-</button>
      <button onClick={increment}>+</button>
    </div>
  );
};

export default Counter;

```

I would like to specify my application entrypoint is still _App.tsx._ It will be modified later in this article to demonstrate our configuration!

```
import Counter from './Counter';

function App() {
  return <Counter />;
}

export default App;

```

### Tests

A complete repository can be found on [GitHub](https://github.com/morintd/rtl-composable-providers).

#### React testing library

After following the [introduction](https://testing-library.com/docs/react-testing-library/intro) and adding [jest-dom](https://testing-library.com/docs/ecosystem-jest-dom/), I set up the first version of my test, as shown below:

```
import { fireEvent, render, screen } from '@testing-library/react';

import Counter from '../Counter';

describe('Counter', () => {
  it('Should display a default value', () => {
    render(<Counter />);
    expect(screen.queryByText('Counter: 0')).toBeInTheDocument();
  });

  it('Should increment', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('+'));
    expect(screen.queryByText('Counter: 1')).toBeInTheDocument();
  });

  it('Should decrement', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('-'));
    expect(screen.queryByText('Counter: -1')).toBeInTheDocument();
  });
});

```

We can find three basic tests. They help us verify weve displayed a default value, which increments or decrements when the corresponding button is clicked.

With the current configuration, Im able to run my test successfully:

![Successful tests](https://cdn.hashnode.com/res/hashnode/image/upload/v1692035718773/1bd3ed32-e553-4647-9e47-0170f2117687.png)

But issues arise when working with a bigger codebase, more functionalities, and dependencies. In this article, I would like to demonstrate how we handle Redux and GraphQL which are fairly common.

#### Adding Redux

Im following [RTK Quick Start](https://redux-toolkit.js.org/tutorials/quick-start), which conveniently shows an example with a counter app. I end up with the following slice:

```
import { createSlice } from '@reduxjs/toolkit';

export interface CounterState {
  value: number;
}

const initialState: CounterState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
  },
});

export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;

```

The following store:

```
import { combineReducers, configureStore } from '@reduxjs/toolkit';

import counterReducer from './counter.slice';

export const rootReducer = combineReducers({
  counter: counterReducer,
});

export const store = configureStore({
  reducer: rootReducer,
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

```

And an updated `Counter.tsx` component:

```
import { useDispatch, useSelector } from 'react-redux';
import { decrement, increment } from './store/counter.slice';

import { RootState } from './store/store';

const Counter = () => {
  const counter = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Counter: {counter}</h1>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  );
};

export default Counter;

```

The behavior of my counter didnt change, but tests are failing with the following error message:

> Could not find react-redux context value; please ensure the component is wrapped in a .

Its quite straightforward. We are trying to test a component in isolation, but it needs a react-redux provider to work. I added it to my `App.tsx`, but from my test perspective, its nowhere to be seen.

Now, for every test, we need to declare a new store and render our component with the Provider from react-redux. Technically, it would work with the following code:

```
import { fireEvent, render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';

import Counter from '../Counter';
import { rootReducer } from '../store/store';

describe('Counter', () => {
  it('Should display a default value', () => {
    const store = configureStore({ reducer: rootReducer });
    render(
      <Provider store={store}>
        <Counter />
      </Provider>
    );

    expect(screen.queryByText('Counter: 0')).toBeInTheDocument();
  });

  it('Should increment', () => {
    const store = configureStore({ reducer: rootReducer });
    render(
      <Provider store={store}>
        <Counter />
      </Provider>
    );

    fireEvent.click(screen.getByText('+'));
    expect(screen.queryByText('Counter: 1')).toBeInTheDocument();
  });

  it('Should decrement', () => {
    const store = configureStore({ reducer: rootReducer });
    render(
      <Provider store={store}>
        <Counter />
      </Provider>
    );

    fireEvent.click(screen.getByText('-'));
    expect(screen.queryByText('Counter: -1')).toBeInTheDocument();
  });
});

```

But, is that the right solution? Thats a lot of unneeded boilerplate code. If you ever have more dependencies, your tests will grow exponentially. It makes them hard to write and maintain.

Instead, React Testing Library explains how to set up a [Custom Render](https://testing-library.com/docs/react-testing-library/setup).

{% embed https://testing-library.com/docs/react-testing-library/setup/#custom-render %}

#### Setup improvement

After following the Custom Render section, I end up creating a tests/ directory with a `testing.tsx` file:

```
import React, { FC, ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { Provider } from 'react-redux';

import { rootReducer } from '../src/store/store';
import { configureStore } from '@reduxjs/toolkit';

const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => {
  const store = configureStore({ reducer: rootReducer });
  return <Provider store={store}>{children}</Provider>;
};

const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => {
  return render(ui, { wrapper: AllTheProviders, ...options });
}

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

```

I add an `index.ts` file that re-exports everything from my tests/ directory Then, instead of importing my Redux boilerplate code and utilities from `@testing-library/react`, I import utilities from this directory:

```
import { fireEvent, render, screen } from '../../tests';
import Counter from '../Counter';

describe('Counter', () => {
  it('Should display a default value', () => {
    render(<Counter />);
    expect(screen.queryByText('Counter: 0')).toBeInTheDocument();
  });

  it('Should increment', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('+'));
    expect(screen.queryByText('Counter: 1')).toBeInTheDocument();
  });

  it('Should decrement', () => {
    render(<Counter />);
    fireEvent.click(screen.getByText('-'));
    expect(screen.queryByText('Counter: -1')).toBeInTheDocument();
  });
});

```

Thats much better! But, what if we need to trigger some change to our Redux store during our test? Also, when our app grows, adding dozens of providers inside our `testing.tsx` will make it harder to read and maintain.

First, I want to make my render more customizable. If you look at the `customRender` method, you can see it takes some options related to React Testing Library.

We can use those options to customize our providers. Ill allow a new property, providers, which is an object with the data related to our providers. For now, it takes the following:

```
export type ProvidersRenderOptions = {
  store?: Store;
};

export type CustomRenderOptions = {
  providers?: ProvidersRenderOptions;
};

```

The implementation looks like this:

```
import { ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { Provider } from 'react-redux';

import { rootReducer } from '../src/store/store';
import { configureStore, Store } from '@reduxjs/toolkit';

export type ProvidersRenderOptions = {
  store?: Store;
};

export type CustomRenderOptions = {
  providers?: ProvidersRenderOptions;
};

const AllTheProviders = (options: ProvidersRenderOptions = {}) => ({ children }: { children: React.ReactNode }) => {
  const store = options.store ?? configureStore({ reducer: rootReducer });
  return <Provider store={store}>{children}</Provider>;
};

const customRender = (ui: ReactElement, options: CustomRenderOptions & Omit<RenderOptions, 'wrapper'> = {}) => {
  const { providers, ...others } = options;
  render(ui, { wrapper: AllTheProviders(providers), ...others });
};

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

```

Looks good, Ill even throw in a helper function to build a store:

```
export const generateStore = (preloadedState: PreloadedState<typeof rootReducer>) => {
  return configureStore({
    preloadedState,
    reducer: rootReducer,
  });
};

```

This way, Im able to write a more advanced test:

```
  it('Should display a default value', () => {
    const store = generateStore({ counter: { value: 5 } });
    render(<Counter />, { providers: { store } });
    expect(screen.queryByText('Counter: 5')).toBeInTheDocument();
  });

```

That looks quite good! But we still have one issue: the `testing.tsx` file will grow to become unmaintainable with the current state of things.

Instead, I would like to move the declaration for providers to different files, and build the function `allTheProviders` on the fly. Also, there is a great pattern to build it, [function composition](https://javascript.plainenglish.io/a-working-example-of-function-composition-in-javascript-5e8c704a13ca)!

Let me explain. Instead of having one huge function, I create one for each provider I want to add. These new functions take options, a React node, and return a React node (with potentially a new provider).

This function, for Redux, would look like this:

```
export const withRedux = (children: ReactElement, options: ProvidersRenderOptions) => {
  return <Provider store={options.store ?? configureStore({ reducer: rootReducer })}>{children}</Provider>;
};

```

If they have this structure, I can chain them, or in other words, compose them, to build a component with multiple providers. If I split my list of providers, the function dedicated to composing them, and the function `AllTheProviders`, it looks like the following:

```
type ComposableProvider = (children: ReactElement, options: ProvidersRenderOptions) => ReactElement;

const providers: ComposableProvider[] = [withRedux];

const composeProviders = (children: ReactElement, options: ProvidersRenderOptions) => {
  return providers.reduce((component, provider) => {
    return provider(component, options);
  }, children);
};

const AllTheProviders = (options: ProvidersRenderOptions = {}) => {
  return ({ children }: { children: ReactElement }) => {
    return composeProviders(children, options);
  };
};

```

And thats it! This way, we can declare new (composable) provider functions, in a different file, and add them to our list.

#### Adding GraphQL

Lets improve our demonstration by adding GraphQL. Well add functionalities to load and save the current counter.

My schema and resolvers look like the following:

```
type Query {
  counter: Int
}

type Mutation {
  setCounter(counter: Int): Int
}


let savedCounter = 0;

export default {
  Query: {
    counter() {
      return savedCounter;
    },
  },
  Mutation: {
    setCounter(_: any, { counter }: { counter: number }) {
      savedCounter = counter;
      return savedCounter;
    },
  },
};

```

After setting up Apollo on the [server](https://www.apollographql.com/docs/apollo-server/getting-started) and [frontend](https://www.apollographql.com/docs/react/get-started), I added two queries to my counter:

```
export const GET_COUNTER = gql`
  query counter {
    counter
  }
`;

export const SET_COUNTER = gql`
  mutation setCounter($counter: Int) {
    setCounter(counter: $counter)
  }
`;

```

Then, I updated my Redux slice, and added two buttons in order to save and load the current counter:

```
export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    load: (state, action: PayloadAction<number>) => {
      state.value = action.payload;
    },
  },
});

export const { increment, decrement, load } = counterSlice.actions;


const Counter = () => {
  const counter = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  const [getCount, query] = useLazyQuery(GET_COUNTER);
  const [mutate, mutation] = useMutation(SET_COUNTER, { variables: { counter } });

  useEffect(() => {
    if (query.data) dispatch(load(query.data.counter));
  }, [query.data]);

  return (
    <div>
      <h1>Counter: {counter}</h1>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(increment())}>+</button>
      <button disabled={mutation.loading} onClick={() => mutate()}>
        save
      </button>
      <button disabled={mutation.loading} onClick={() => getCount()}>
        load
      </button>
    </div>
  );
};

```

But now, just like for Redux, our tests throw an error:

> Invariant Violation: Could not find client in the context or passed in as an option. Wrap the root component in an , or pass an ApolloClient instance in via options.

Lets follow the [testing section](https://www.apollographql.com/docs/react/development-testing/testing/) from Apollo, and integrate it into our custom render. We can start by adding an option for GraphQL mocks and create a composable test provider for apollo:

```
export type ProvidersRenderOptions = {
  store?: Store;
  graphql?: MockedResponse[];
};


import { ReactElement } from 'react';
import { MockedProvider } from '@apollo/client/testing';

import { ProvidersRenderOptions } from '../testing';

export function withApollo(children: ReactElement, options: ProvidersRenderOptions) {
  return (
    <MockedProvider mocks={options.graphql ?? []} addTypename={false}>
      {children}
    </MockedProvider>
  );
}

```

Then, we can add this composable provider to our `providers`. Keep in mind the order of `providers` will have an impact on how our providers are added.

This is related to how function composition works. The most left in the list will be the inner `providers`, while the most right will be the outer `providers`.

```
const providers: ComposableProvider[] = [withRedux, withApollo];

```

Then, Im able to write the following test:

```
  it('Should load counter', async () => {
    const query = { request: { query: GET_COUNTER }, result: { data: { counter: 5 } } };
    render(<Counter />, { providers: { graphql: [query] } });

    fireEvent.click(screen.getByText('load'));

    await waitFor(() => {
      expect(screen.queryByText('Counter: 5')).toBeInTheDocument();
    });
  });

  it('Should save counter', async () => {
    const query = {
      request: { query: SET_COUNTER, variables: { counter: 1 } },
      result: jest.fn().mockReturnValue({ data: { setCounter: 1 } }),
    };

    render(<Counter />, { providers: { graphql: [query] } });

    fireEvent.click(screen.getByText('+'));
    fireEvent.click(screen.getByText('save'));

    await waitFor(() => {
      expect(query.result).toHaveBeenCalled();
    });
  });

```

And thats it! You are now able to write proper tests in a healthy environment. Moreover, you wont have any issues when your app gets bigger, as long as you continue to create composable test providers.

Dont forget a complete repository is available on [GitHub](https://github.com/morintd/rtl-composable-providers).

* * *

Cover photo by [sharonmccutcheon](https://unsplash.com/@sharonmccutcheon) on [Unsplash](https://unsplash.com/s/photos/disorder)