reduxjs / redux-mock-store

A mock store for testing Redux async action creators and middleware.
MIT License
2.5k stars 149 forks source link

does not work with nested async thunks? #176

Open bcolloran opened 4 years ago

bcolloran commented 4 years ago

adapted from the async example in the README:

import configureStore from "redux-mock-store";
import thunk from "redux-thunk";

const middlewares = [thunk]; // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares);

// You would import the action from your codebase in a real scenario
function success() {
  return {
    type: "FETCH_DATA_SUCCESS"
  };
}

function success_innerAsync() {
  return {
    type: "FETCH_DATA_SUCCESS--INNER_DISPATCH"
  };
}

function fetchData() {
  return async dispatch => {
    await fetch("/users.json"); // Some async action with promise
    dispatch(success());
    dispatch(fetchMoreData_innerAsync());
  };
}

function fetchMoreData_innerAsync() {
  return async dispatch => {
    await fetch("/more-data.json"); // Some async action with promise
    dispatch(success_innerAsync());
  };
}

it("should execute fetch data", () => {
  const store = mockStore({});

  // Return the promise
  return store.dispatch(fetchData()).then(() => {
    const actions = store.getActions();
    expect(actions).toEqual([success(), success_innerAsync()]);
  });
});

What I'm expecting here is that the thunk fetchData dispatches success() to the store and also dispatches the thunk fetchMoreData_innerAsync; this second thunk should dispatch success_innerAsync() to the store. So i would expect e.g.:

expect(store.getActions()).toEqual([success(), success_innerAsync()]);

But instead, the result is:

Expected value to equal:
  [{"type": "FETCH_DATA_SUCCESS"}, {"type": "FETCH_DATA_SUCCESS--INNER_DISPATCH"}]
Received:
  [{"type": "FETCH_DATA_SUCCESS"}]

Difference:

- Expected
+ Received

Array [
    Object {
      "type": "FETCH_DATA_SUCCESS",
    },
-   Object {
-     "type": "FETCH_DATA_SUCCESS--INNER_DISPATCH",
-   },
  ]

Here's a code sandbox demonstrating:

https://codesandbox.io/s/fragrant-smoke-e0egv?file=/src/index.test.js

This is just a minimal example for to demonstrate, this kind of usage of async thunks in real code seems to work fine in my application. Hence, I don't think I'm abusing Redux/thunks somehow (though please let me know if i am!), but rather that I'm either not understanding how to properly use the mock store, or that there's a bug that only captures the top level of async thunks.

(Or perhaps this will turn out to be a feature request for some kind of new API that expands on .getActions to await all nested async actions/thunks somehow...)

Thanks for any advice!

bcolloran commented 4 years ago

Update: this definitely seems to be a tricky race condition kind of thing, because inserting a brief delay allows the tests to pass.

Here's an updated sandbox https://codesandbox.io/s/nifty-hofstadter-wnouo

And I'll put the updated passing code here as well for posterity:

import configureStore from "redux-mock-store";
import thunk from "redux-thunk";

const middlewares = [thunk]; // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares);

// You would import the action from your codebase in a real scenario
function success() {
  return {
    type: "FETCH_DATA_SUCCESS"
  };
}

function success_innerAsync() {
  return {
    type: "FETCH_DATA_SUCCESS--INNER_DISPATCH"
  };
}

function fetchData() {
  return async dispatch => {
    await fetch("/users.json"); // Some async action with promise
    dispatch(success());
    dispatch(fetchMoreData_innerAsync());
  };
}

function fetchMoreData_innerAsync() {
  return async dispatch => {
    await fetch("/more-data.json"); // Some async action with promise
    dispatch(success_innerAsync());
  };
}

async function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

it("should execute fetch data", async () => {
  const store = mockStore({});

  // Return the promise
  await store.dispatch(fetchData());
  await wait(1000);
  const actions = store.getActions();
  expect(actions).toEqual([success(), success_innerAsync()]);
});
landonalder commented 4 years ago

I'm having this same issue, but the wait makes no difference for me. Redux mock store version is 1.5.4 and react-redux version is 7.1.3. Any additional async actions that are dispatched inside an action creator are never called

Edit: I actually figured out the reason that my async thunk that was dispatched inside the async action I was testing wasn't getting called because there was an exception inside it. Sadly, there's no indication of this except for when you call store.getActions(), I saw an error action that was dispatched with the error message.

garyee commented 3 years ago

@landonalder thanks!! I had the same error and after reading your comment about the error not showing. I search for and discovered the error. It is working now!

landonalder commented 3 years ago

@garyee glad to hear this helped :)