Redux-toolkit: What is the best way to mock the thunkApi for testing a createAsyncThunk?

Created on 22 Jun 2020  路  10Comments  路  Source: reduxjs/redux-toolkit

Following the cancellation example in the docs, https://redux-toolkit.js.org/api/createAsyncThunk#examples, I'm finding it difficult to test the request ID matching portion.

I was looking at the createAsyncThunk.test.js tests in the src, but those seem to just save the value that gets generated rather than specifying the value _to be generated_

If we have a function like

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  async (userId, { getState, requestId, dispatch }) => {
    const { currentRequestId, loading } = getState().users
    if (loading !== 'pending' || requestId !== currentRequestId) {
      dispatch(someSecondaryFunction());
      return
    }
    const response = await userAPI.fetchById(userId)
    return response.data
  }
)

is there a way to ensure that requestId will return a predefined value?

Most helpful comment

Hey, sorry for necro. I just posted on a similar issue that this question is related to. I hope this helps anyone who stumbles upon this slightly_smiling_face.
#494 (comment)

I love your solution but without seeing the rest of the code.. how the api is setup it is impossible to follow , sorry

I updated the original code to demonstrate how to interact with the API using Axios in a clean fashion. I included _actual_ production code in the shared/http.ts file, so you can derive this and adjust as you need it.

All 10 comments

Mmm... maybe using Jest/Sinon mocks to override our nanoid import before testing the thunk? Not sure, haven't tried anything like that myself.

But really, ask yourself the question: does fixating that value make your test better, or are you adding a simplification that will make your test actually worse?

In reality, that requestId will be a random value, with a guarantee to be equal per set of pending/fulfilled/rejected grouping, but separate from each call.
What good does it to your test, if it is always equal?

@phryneas My idea is that we'd want to be able to test that the correct resolution is taken if the function encounters a matching id (it should fire off the userAPI request) or a mismatching id (the function should short circuit and dispatch someSecondaryFunction).

I wouldn't want it to be _always_ equal; but I would like to be able to make it equal on demand to be able to assert that the matching ID path gets taken.

EDIT: so something like

const mockStore = configureMockStore([thunk]);
store = mockStore({
   data: {
     currentRequestId: 1,
  }
});
it('should fire userAPI request on match' () => {
  // mock requestId to be 1
  await fetchUserById('77');
  expect(userApiMock.fetchById).toHaveBeenCalled()
}
it('should not fire userAPI request when request already in flight' () => {
  // mock requestId to be anything other than 1 (aka no mock at all)
  await fetchUserById('77');
  expect(userApiMock.fetchById).not.toHaveBeenCalled()
}

Why not just call it twice and check that it was actually just executed once?
That would mimic "real world" behaviour. Granted, it would be more of an integration test than a unit test, but you're already at the point where you're testing RTK as much as your own code, so you're very much in "integration test" territory there anyways ;)

Fair point - I also realized that if testing the pathway is that important, I can extract the payloadCreator callback to a separate function and mock the thunkApi argument trivially there; appreciate the discussion guys.

EDIT: For anyone stumbling on this later, I mean refactoring the above to

const fetchUserLogic = async (userId, { getState, requestId, dispatch }) => {
    const { currentRequestId, loading } = getState().users
    if (loading !== 'pending' || requestId !== currentRequestId) {
      dispatch(someSecondaryFunction());
      return
    }
    const response = await userAPI.fetchById(userId)
    return response.data
  }

const fetchUserById = createAsyncThunk(
  'users/fetchByIdStatus',
  fetchUserLogic
)

where it become easy to inject the second argument into fetchUserLogic tests (and coming up with a better name)

Hey, sorry for necro. I just posted on a similar issue that this question is related to. I hope this helps anyone who stumbles upon this :slightly_smiling_face:.

https://github.com/reduxjs/redux-toolkit/issues/494#issuecomment-772487032

Hey, sorry for necro. I just posted on a similar issue that this question is related to. I hope this helps anyone who stumbles upon this 馃檪.

#494 (comment)

I love your solution but without seeing the rest of the code.. how the api is setup it is impossible to follow , sorry

Hey, sorry for necro. I just posted on a similar issue that this question is related to. I hope this helps anyone who stumbles upon this slightly_smiling_face.
#494 (comment)

I love your solution but without seeing the rest of the code.. how the api is setup it is impossible to follow , sorry

I updated the original code to demonstrate how to interact with the API using Axios in a clean fashion. I included _actual_ production code in the shared/http.ts file, so you can derive this and adjust as you need it.

Hey, sorry for necro. I just posted on a similar issue that this question is related to. I hope this helps anyone who stumbles upon this slightly_smiling_face.
#494 (comment)

I love your solution but without seeing the rest of the code.. how the api is setup it is impossible to follow , sorry

I updated the original code to demonstrate how to interact with the API using Axios in a clean fashion. I included _actual_ production code in the shared/http.ts file, so you can derive this and adjust as you need it.

thank you so much rachael!
I love how you handle network services with ... api.method . i been using api.get(endpoint.blah) , that might chance now ;) !

Hey, sorry for necro. I just posted on a similar issue that this question is related to. I hope this helps anyone who stumbles upon this slightly_smiling_face.
#494 (comment)

I love your solution but without seeing the rest of the code.. how the api is setup it is impossible to follow , sorry

I updated the original code to demonstrate how to interact with the API using Axios in a clean fashion. I included _actual_ production code in the shared/http.ts file, so you can derive this and adjust as you need it.

thank you so much rachael!
I love how you handle network services with ... api.method . i been using api.get(endpoint.blah) , that might chance now ;) !

You're welcome :slightly_smiling_face:. If you have any other specific questions, I'm always available on Discord

Was this page helpful?
0 / 5 - 0 ratings