Mocking RTK Query API with Mock Service Worker for testing React Native Apps
Tags: rtkq, msw, reactnative, testing
Published: 2022-03-10 06:55:57

Today I will show you how I integrated the RTK Query RESTful API handler with Mock Service Worker for testing React Native project with Jest. 

### About the RTK Query
[RTK Query](https://redux-toolkit.js.org/rtk-query/overview) (short RTKQ) is a tool shipped with the Redux Toolkit package designed for fetching and caching data.

### What is Mock Service Worker and why should you use it?
[Mock Service Worker](https://mswjs.io/) (short MSW) is a library that allows you to intercept the actual requests at the highest level of the network communication chain and return mocked responses.

Mocking API with MSW lets you forget about request mocks inside the individual test files. It's relatively easy to set up and helps to keep your code more concise and clear.

### Prerequisites
- React Native project with RTKQ
- node.js v16 or higher (MSW requirement)

### Setup

#### 1. Add necessary MSW package and fetch polyfills that React Native has not defined.
`yarn add -D MSW cross-fetch abort-controller`

#### 2. Create the jestSetup.js file with the following configuration.

- MSW configuration

**`<rootDir>/jestSetup.js`**
```JAVASCRIPT
import { server } from './src/mocks/api/server'

// 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())
// Clean up after the tests are finished.
afterAll(() => server.close())
```

- Since Jest runs tests in the node.js environment, and we test the code meant to run in the browser, the browser globals like fetch / Headers / Request are not available there. We need to add these polyfills to fix it.

**`<rootDir>/jestSetup.js`**
```JAVASCRIPT
import AbortController from 'abort-controller'
import { fetch, Headers, Request, Response } from 'cross-fetch'

global.fetch = fetch
global.Headers = Headers
global.Request = Request
global.Response = Response
global.AbortController = AbortController
```

#### 3. Tell Jest about the jestSetup configuration file by setting the setupFilesAfterEnv option.

**`<rootDir>/jest.config.js`**
```JAVASCRIPT
module.exports = {
 setupFilesAfterEnv: ['<rootDir>/jestSetup.js']
}
```

#### 4. Define MSW handlers and server.

As you can see, you can use a wildcard character in the path.

**`<rootDir>/src/mocks/api/handlers.ts`**
```JAVASCRIPT
import { rest } from 'msw'

export const handlers = [
rest.get(`${POKEMON_API_BASE_URL}/pokemon/*`, (req, res, ctx) => {
  return res(ctx.status(200), ctx.json(dummyPokemon))
 })
]
```

**`<rootDir>/src/mocks/api/server.ts`**
```JAVASCRIPT
import { setupServer } from 'msw/node'

import { handlers } from './handlers'

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

#### 5. Change RTKQ cache behavior under the test environment to avoid following Jest warning:

> Jest did not exit one second after the test run has completed.

**`<rootDir>/src/api/pokemonAPI.ts`**
```JAVASCRIPT
export const api = createApi({
 keepUnusedDataFor: process.env.NODE_ENV === 'test' ? 0 : 60
})
```

I also stumbled upon the following error while testing the custom useDebounce hook.

> Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

I fixed it by upgrading jest to ^27.5.1, @types/jest to ^27.4.1, and babel-jest to ^27.5.1.

And that's all! From now you can test all your components seamlessly with mocked API.

At the end an example test
`<rootDir>/src/screens/HomeScreen/components/PokemonFinder/PokemonFinder.test.tsx`
```JAVASCRIPT
beforeEach(() => {
	jest.useFakeTimers()
})

afterEach(() => {
	jest.useRealTimers()
})

test('PokemonFinder should display image with proper source', async () => {
 const store = setupStore()
 const { findByTestId, getByPlaceholderText } = 
 renderWithProviders(
  <PokemonFinder />,
  { store }
 )
 const searchInput = getByPlaceholderText(placeholder)
 fireEvent.changeText(searchInput, name)
 act(() => {
  jest.advanceTimersByTime(delay)
 })
 const image = await findByTestId('official-artwork')
 expect(image.props.source.uri).toBe(sprite)
})
```

Hope you find this article useful 😃

You can see the full Github repository [here](https://github.com/JBudny/PokemonFinder)

## Conclusion
Mock Service Worker proves that mocking requests does not need to occur all over your tests. It offers a little more concise code for a reasonable amount of boilerplate. As a result, it makes our environment friendlier.

## Acknowledgments

I decided to try MSW with React Native after reading this article.
Thanks to Kent C. Dodds for [stop-mocking-fetch](https://kentcdodds.com/blog/stop-mocking-fetch)