How to test RTK Query with react testing library
Tags: react, api, testing, javascript
Published: 2022-08-13 14:13:00

Testing components with a request in rtk-query using msw and react-testing-library.

Hello everyone, I started testing a react web app and my requests for fetching and uploading data are made using rtk-query. I will guide you on how to write tests for components when using rtk query.

First, check out my tutorial on [how to set up rtk query in redux toolkit](https://dev.to/masterifeanyi/how-to-setup-redux-tool-kit-for-rtk-query-2o69).



> You can use a library like [msw](https://mswjs.io/) to mock your api - that's what we use in the Redux Toolkit codebase to test RTK Query.
> 
> <cite>phryneas</cite> (Redux toolkit maintainer)

```
    npm install msw --save-dev
```

To test RTK Query with react testing library? there are three steps, 

1. use `msw` to mock your API.
2. wrap your component in a real Redux store with your API.
3. write your tests - use something to wait for UI changes.

**Set up a custom render function**

We need a custom render function to wrap our components when testing. This function is called `renderWithProviders` To learn [more](https://redux.js.org/usage/writing-tests#setting-up-a-reusable-test-render-function)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sk2mfw4re6ubhxm6c5af.gif)


```javascript

// ./src/test-utils.js

import React from 'react'
import { render } from '@testing-library/react'
import { Provider } from 'react-redux'
import { setupStore } from './app/store'
import { setupListeners } from '@reduxjs/toolkit/dist/query'

export function renderWithProviders(
  ui,
  {
    preloadedState = {},
    // Automatically create a store instance if no store was passed in
    store = setupStore(preloadedState),
    ...renderOptions
  } = {}
) {

  setupListeners(store.dispatch);

  function Wrapper({ children }) {
    return <Provider store={store}>{children}</Provider>
  }
  
  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) }
}

```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcv6u1rqfate9c2lzcvz.gif)


**Redux store**

we would set-up our redux store a little differently, for more info check [here](https://redux.js.org/usage/writing-tests#setting-up-a-reusable-test-render-function)

```javascript

// ./src/app/store.js


import { configureStore } from "@reduxjs/toolkit";
import { apiSlice } from "./api/apiSlice";



export const setupStore = preloadedState => {
  return configureStore({
    reducer: {
      [apiSlice.reducerPath]: apiSlice.reducer,
    },
    preloadedState,
    middleware: getDefaultMiddleware =>
        getDefaultMiddleware({
    immutableCheck: false,
    serializableCheck: false,
  }).concat(apiSlice.middleware),
  })
}
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwm2r81a1ngr1dvm7odx.gif)

**Provide the store to the App**

We need to wrap our react app with the redux store we have set up 

```javascript

// ./src/index.js

import { setupStore } from './app/store'
import { Provider } from 'react-redux';

const store = setupStore({});

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
```
## Set up test environment

Before we begin, we have to set up our test environment in JEST


`setupTests.js`

```javascript
// ./src/setupTests.js

import '@testing-library/jest-dom';
import { server } from './mocks/api/server'
import { apiSlice } from './app/api/apiSlice'
import { setupStore } from './app/store'

const store = setupStore({});


// Establish API mocking before all tests.
beforeAll(() => {
    server.listen();
});

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => {
    server.resetHandlers();
    // This is the solution to clear RTK Query cache after each test
    store.dispatch(apiSlice.util.resetApiState());
});

// Clean up after the tests are finished.
afterAll(() => server.close());

```



We reset the API between the tests, as the API has internal state as well, by calling `store.dispatch(apiSlice.util.resetApiState());` after each test


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dfdw7wu2t9fea2lvaw7z.gif)


## Mocking REST API


We use `msw` to mimic (mock) the API request we make in our App. I will show you how to set up and use `msw`.

## msw 

In your `src` directory, create a folder `mock` and a sub-folder `api`


**API handler**

The handler contains the global set up for a successful request, if the API was mocked (queried) successfully, the response will be taken from what we have defined in the `msw` response object.


`./src/mock/api/handler.js`


```javascript

// ./src/mock/api/handler.js

import { rest } from 'msw'

export const handlers = [
  rest.get('https://jsonplaceholder.typicode.com/users', (req, res, ctx) => {
    
    // successful response
    return res(ctx.status(200), ctx.json([
        { id: 1, name: 'Xabi Alonzo' },
        { id: 2, name: 'Lionel Messi' },
        { id: 3, name: 'Lionel Love' },
        { id: 4, name: 'Lionel Poe' },
        { id: 5, name: 'Lionel Gink' },
    ]), ctx.delay(30))
  })
]
```



`./src/mock/api/server.js`


```javascript

// ./src/mock/api/server.js

import { setupServer } from 'msw/node'

import {handlers} from "./handler"

export const server = setupServer(...handlers)
```

Finally, writing tests

## Test 1: Fetch from API

To handle a REST API request we need to specify its method, path, and a function that would return the mocked response. [learn more](https://mswjs.io/docs/getting-started/mocks/rest-api#request-handler). 

This is our URL structure: 

baseUrl: `"https://api.coingecko.com/api/v3"`

Query Parameters: `?vs_currency=ngn&order=market_cap_desc&per_page=100&page=1`


The intercepted request is the request we make in our component.

```javascript
const queryRequest = {
  vs_currency: "usd",
  order: "market_cap_desc",
  per_page: "10",
  sparkline: "false",
  page
}

const {
  data: coins,
  isSuccess,
  isError,
  error,
  isLoading
} = useGetCoinsQuery(queryRequest)

getCoins: builder.query({
  query: (arg) => ({
    url: `/coins/markets`,
    params: {...arg}
  }),
  providesTags: ["coins"],
})
```

The test; fetching data from an API

```javascript

// ./src/__test__/Crypto.test.js

const apiData = [
  {name: "Mark Zuckerberg", age: "34"},
  {name: "Elon Musk", age: "44"}
]

test("table should render after fetching from API depending on request Query parameters", async () => {

    // custom msw server
    server.use(
      rest.get(`*`, (req, res, ctx) => {
          const arg = req.url.searchParams.getAll("page");
          console.log(arg)
          return res(ctx.json(apiData))         
        }
      ) 
	  );


    // specify table as the render container
    const table = document.createElement('table')
    
    // wrap component with custom render function
    const { container } = renderWithProviders(<Coins />, {
      container: document.body.appendChild(table),
    });


    const allRows = await screen.findAllByRole("row")

    await waitFor(() => {
        expect(container).toBeInTheDocument();
    })  

    await waitFor(() => {
        expect(allRows.length).toBe(10);
    })
})
```

<a href="https://www.buymeacoffee.com/ifeanyichima" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>


**explaining the test**

1. create a custom server :- For each test, we can over-ride the API handler to test individual sceneraios, by creating a custom `msw` server.
2. `req.url.searchParams.getAll` :- We use this to get all the query parameters that was sent with the request.
3. apiData :- this is the response we expect to be returned by the API.
4. wrap table with container :- According to the RTL (react testing library) [documentation](https://testing-library.com/docs/react-testing-library/api#container), we need to specify table as the render container.
5. wrap the component :- we wrap the component we want to test with our custom reder function.
6. wildcard (*) :- We use this to represent the api URL.
7. get all `tr` element :- I want to get all `tr` element, so that I can check if we have up to 10 rows in the table. To do that I use `row`, you can learn more [here](https://html.spec.whatwg.org/multipage/tables.html#the-tbody-element)

<a href="https://www.buymeacoffee.com/ifeanyichima" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>

## Test 2: Mocking error responses

If you want to write test for an error sceneraio such as when the API server is unavailable.

The intercepted request

```javascript

{isError && (<p data-testid="error" className="text-center text-danger">Oh no, there was an error {JSON.stringify(error.error)} </p>)}
                      
{isError && (<p data-testid="customError" className="text-center text-danger">{error.data.message}</p>)} 
```

The test; mocking error sceneraio

```javascript

// ./src/__test__/Crypto.test.js

test('renders error message if API fails on page load', async () => {
    server.use(
      rest.get('*', (_req, res, ctx) =>
        res.once(ctx.status(500), ctx.json({message: "baby, there was an error"}))
      )
    );

    renderWithProviders(<Coins />);

    const errorText = await screen.findByTestId("error");

    const errorMessage = await screen.findByTestId("customError");

    await waitFor(() => {
        expect(errorMessage.textContent).toBe("baby, there was an error")
    })

    await waitFor(() => {
        expect(errorText.textContent).toBe("Oh no, there was an error");
    })
});

```

**explaining the test**

1. create a custom server :- For each test, we can over-ride the API handler to test individual sceneraios, by creating a custom `msw` server.
2. we do not need the req `argument` because we are testing for error.
3. wrap the component :- we wrap the component we want to test with our custom reder function.
4. wildcard (*) :- We use this to represent the API URL.
5. res status code :- we intentionally throw an error with status code (500) to test for error.
6. response body :- we pass in error message as an object to the response body.




## reference

[Testing components with a request for rtk-query](https://stackoverflow.com/questions/73059793/testing-components-with-a-request-for-rtk-query)

[Testing React-query with MSW](https://stackoverflow.com/questions/69762253/testing-react-query-with-msw-with-the-data-passed-to-a-child-component)