Writing Jest Tests For a Redux Toolkit Slice
Tags: redux, react, axios, testing
Published: 2021-11-20 21:05:25

---
title: Writing Jest Tests For a Redux Toolkit Slice
published: true
date: 2021-11-20 00:00:00 UTC
tags: redux, react, axios, testing
description: Including asynchronous and synchronous actions
canonical_url: https://bionicjulia.com/blog/writing-jest-tests-redux-toolkit-slice
---

I've been doing a fair amount of work recently with Redux Toolkit (RTK), for a new feature I'm building. I'm also trying to be a lot stricter with ensuring I've got tests for all the key parts of the code I've written, and so, have also been delving deeper into writing Jest tests for RTK.

The way I learn how to write tests is by following along to good examples. I therefore thought I'd write this blog post as a way to help others who might also be going through this process, but also as a record for myself, as I'm sure I'll be writing similar tests in the future.

## Scene setting

To set the context, let's say we've set up our RTK slice for a gaming app we're creating. This `Games` slice has a state that's basically an object of objects. It allows for an asynchronous `fetchGamesSummary` action that calls an external API, and a synchronous `updateGameInterest` action.

- The `fetchGamesSummary` async thunk is called with a `userId` and returns a list of games that looks like this:
  ```jsx
  {
  	call_of_duty: {
  		interest_count: 10,
  		key: "call_of_duty",
  	  user_is_interested: true,
  	},
  	god_of_war: {
  		interest_count: 15,
  		key: "god_of_war",
  	  user_is_interested: false,
  	},
  	//...
  }
  ```
- The `updateGameInterest` action is effected by a button toggle, where a user is able to toggle whether they are interested (or not) in a game. This increments/decrements the `interestCount`, and toggles the `userIsInterested` value between true/false. Note, the camelcase is because it relates to frontend variable. Snake case is what's received from the API endpoint.

```jsx
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'

export const initialStateGames: TStateGames = {
  games: {},
}

export const fetchGamesSummary = createAsyncThunk('games/fetch_list', async (userId: string) => {
  const response = await gamesService.list(userId)
  return response
})

export const gamesSlice = createSlice({
  initialState: initialStateGames,
  name: 'Games',
  reducers: {
    updateGameInterest: (state, action: PayloadAction<TUpdateGameInterestAction>) => ({
      ...state,
      games: {
        ...state.games,
        [action.payload.gameKey]: {
          ...state.games[action.payload.gameKey],
          interest_count: state.games[action.payload.gameKey].interest_count + action.payload.interestCount,
          user_is_interested: action.payload.userIsInterested,
        },
      },
    }),
  },
  extraReducers: {
    [fetchGamesSummary.fulfilled.type]: (state, action: { payload: TGames }) => {
      const games = action.payload

      return {
        ...state,
        games,
      }
    },
  },
})
```

I haven't shown it here, but upon defining your new slice, you're also going to need to ensure the reducer is added to your `combineReducers`. e.g.

```jsx
export default combineReducers({
  games: gamesSlice.reducer,
  // your other reducers
})
```

Side note: If you want to see the types, scroll down to the Appendix below.

## Jest tests

There are a few different things I want to test my RTK slice for. My tests' `describe` looks like this:

- Games redux state tests...
  - Should initially set games to an empty object.
  - Should be able to fetch the games list for a specific user.
  - Should be able to toggle interest for a specific game.

### Should initially set games to an empty object

I'm going to assume you've already got your Jest config setup for your app. This first test checks that we can connect to our store and specific slice.

```jsx
import store from './store'

describe('Games redux state tests', () => {
  it('Should initially set games to an empty object', () => {
    const state = store.getState().games
    expect(state.games).toEqual({})
  })
})
```

Your `store` is where you set up your `configureStore`. See the documentation [here](https://redux.js.org/api/store) for more info. `getState()` is a method that returns the current state tree, from which I'm particularly interested in the `games` slice.

### Should be able to fetch the games list for a specific user

This test requires some initial setup as we'll be calling an external API. This bit might differ for you, as it'll depend on how you call your API. I have mine set up through an `ApiClient` class, which I use to set up my base API Axios settings. If you're interested in learning more about this, read my previous blog post on [Axios wrappers](https://bionicjulia.com/blog/axios-wrappers-react-typescript). In this app, I've defined a `getClient()` method within my `ApiClient` class that returns an `AxiosInstance`.

For the purposes of testing, I don't actually want to make an API call, so I mocked the API request through the use of `axios-mock-adapter`. There are other packages available, so browse around for whatever works best for you. The `MockAdaptor` takes in an Axios instance as an argument, and from there, enables you to mock call your GET endpoint with your defined mock response. Note here that the API endpoint `/games/list/?user_id=${userId}` is in effect what my `gamesService.list(userId)` calls in my `fetchGamesSummary` function above.

```jsx
import ApiClient from '../api/ApiClient'
import MockAdapter from 'axios-mock-adapter'
import store from '../../store'

const userId = 'test123'

const getListResponse = {
  game_1: {
    interest_count: 0,
    key: 'game_1',
    user_is_interested: false,
  },
}

const apiClient = new ApiClient()

const mockNetworkResponse = () => {
  const mock = new MockAdapter(apiClient.getClient())
  mock.onGet(`/games/list/?user_id=${userId}`).reply(200, getListResponse)
}
```

When writing the test, I needed to:

- Dispatch the `fetchGamesSummary` async action.
- Check the result type was `fulfilled` i.e. matches how I defined my `extraReducers`.
- Check that the result from the dispatch matches the mock response.
- Check that the `games` state reflects what I fetched from the API.

Putting it all together then...

```jsx
import ApiClient from '../api/ApiClient'
import MockAdapter from 'axios-mock-adapter'

import store from '../../store'
// import your slice and types

const userId = 'test123'
const getListResponse = {
  game_1: {
    interest_count: 0,
    key: 'game_1',
    user_is_interested: false,
  },
}

const apiClient = new ApiClient()

const mockNetworkResponse = () => {
  const mock = new MockAdapter(apiClient.getClient())
  mock.onGet(`/games/list/?user_id=${userId}`).reply(200, getListResponse)
}

describe('Games redux state tests', () => {
  beforeAll(() => {
    mockNetworkResponse()
  })

  it('Should be able to fetch the games list for a specific user', async () => {
    const result = await store.dispatch(fetchGamesSummary(userId))
    const games = result.payload

    expect(result.type).toBe('games/fetch_list/fulfilled')
    expect(games.game_1).toEqual(getListResponse.game_1)

    const state = store.getState().games
    expect(state).toEqual({ games })
  })
})
```

### Should be able to toggle interest for a specific game

With everything set up nicely now, this final test is relatively simpler to write. Just be sure to include the `beforeAll` block calling the `mockNetworkResponse()` (since ultimately, all your tests will be in this one file).

When writing this test, I needed to:

- Dispatch the `fetchGamesSummary` async action to fill out our `games` state.
- Dispatch the `updateGameInterest` action.
- Check that the `games` state updates the `interestCount` and `userIsInterested` values correctly.

```jsx
import ApiClient from '../api/ApiClient'
import MockAdapter from 'axios-mock-adapter'

import store from '../../store'
// import your slice and types

const userId = 'test123'
const getListResponse = {
  game_1: {
    interest_count: 0,
    key: 'game_1',
    user_is_interested: false,
  },
}

const apiClient = new ApiClient()

const mockNetworkResponse = () => {
  const mock = new MockAdapter(apiClient.getClient())
  mock.onGet(`/games/list/?user_id=${userId}`).reply(200, getListResponse)
}

describe('Games redux state tests', () => {
  beforeAll(() => {
    mockNetworkResponse()
  })

  it('Should be able to toggle interest for a specific game', async () => {
    await store.dispatch(fetchGamesSummary(userId))

    store.dispatch(
      gamesSlice.actions.updateGameInterest({
        interestCount: 1,
        userIsInterested: true,
        gameKey: 'game_1',
      }),
    )

    let state = store.getState().games
    expect(state.games.game_1.interest_count).toBe(1)
    expect(state.games.game_1.userIsInterest).toBe(true)

    store.dispatch(
      gamesSlice.actions.updateGameInterest({
        interestCount: -1,
        userIsInterested: false,
        gameKey: 'game_1',
      }),
    )
    state = store.getState().games
    expect(state.games.game_1.interest_count).toBe(0)
    expect(state.games.game_1.userIsInterest).toBe(false)
  })
})
```

And that's it! I came up with this example solely for the purpose of this blog post, so didn't actually test that the code works. 😅 If you come across any suspected errors, let me know. Or, if you come up with a better way of testing my cases, I'd be all ears! 😃

Talk to me on [Twitter](https://twitter.com/bionicjulia), [Instagram](https://instagram.com/bionicjulia) or my website https://bionicjulia.com

## Appendix

Types

```jsx
export type TGame = {
  interest_count: number,
  key: string,
  user_is_interested: boolean,
}

export type TGames = { string: TGame } | {}

export type TStateGames = {
  games: TGames,
}

export type TUpdateGameInterestAction = {
  gameKey: string,
  userIsInterested: boolean,
  interestCount: number,
}
```