redux-observable: Duplicate actions in stream when used with redux-mock-store

Do you want to request a feature or report a bug?

A bug I think, but not sure if it’s redux-observable or redux-mock-store who has the wrong behavior. So I will post in both repos.

What is the current behavior?

When writing a single test with jest and redux-mock-store, everything works as expected. But if I use the mockStore multiple times (in the same test or even in another), then the actions dispatched in any of the created stores are sent multiple times in the observable (but only once in the store, as store.getActions() states.

Here’s a reproduction repo : https://framagit.org/jalil/redux-observable-mock-store-duplicate

tldr;

This works :

const store = mockStore();
store.dispatch({ type: 'FOO' }); // -> Observable get 1 FOO action

This doesn’t :

const store = mockStore();
store2 = mockStore();
mockStore();
store.dispatch({ type: 'FOO' }); // => Observable get 3 FOO actions
const store = mockStore();
store2 = mockStore();
mockStore();
mockStore();
mockStore();
store.dispatch({ type: 'FOO' }); // -> Observable get 5 FOO actions

… and so on …

What is the expected behavior?

I expect, as I use replaceEpic and mockStore, and I use different jest tests, that one test should not affect another, and one mockStore should not affect another.

So, I expect, even if I have multiple tests each one calling mockStore(), to have my epics receiving the right stream of actions.

Which versions of redux-observable, and which browser and OS are affected by this issue? Did this work in previous versions of redux-observable?

My above reproduction code uses :

"redux": "^3.7.2",
"redux-observable": "^0.17.0",
"rxjs": "^5.5.6"
"jest": "^22.0.4",
"redux-mock-store": "^1.4.0"

Thanks for your help.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 6
  • Comments: 18 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Cross post from your StackOverflow question https://stackoverflow.com/questions/48045348/duplicate-actions-in-redux-observable-stream-when-used-with-redux-mock-store/48045589#48045589

redux-observable currently makes the assumption that the middleware function returned by createEpicMiddleware() won’t be called multiple times. redux-mock-store appears to call it every time you call mockStore. While this behavior is probably not officially documented, my gut tells me redux-observable should not be making this assumption. Similar libraries like redux-saga used to as well, but they might have stopped.

It’s not immediately clear why this has never been noticed before by anyone. I don’t see any notable changes to either library that would have introduced this recently. My best guess is that it didn’t have any perceivable side effect so no one noticed. e.g. your example doesn’t filter any actions or emit any, instead just always logging with .do().

I’m on holiday right now, so I can’t dig into this until later this week. Sorry! But you can work around it by creating a new epicMiddleware and calling configureMockStore for every test instead of reusing it. One example might be something like this:

const mockStore = (...args) => {
  const epicMiddleware = createEpicMiddleware(someEpic);
  const factory = configureMockStore([epicMiddleware]);
  return factory(...args);
};

Adjust to your needs; e.g. if you need to change the root epic.

I guess no one saw it because no one tests epics

Dunno if this was a joke, but this is very very not true lol 😄 I’ve seen a large number of projects testing epics this way or similar.

Separately, a growing number of people test individual epics directly by just calling them with their own ActionsObservable, not using redux or any middleware. e.g. https://redux-observable.js.org/docs/recipes/InjectingDependenciesIntoEpics.html This is how I recommend people test and what the docs will suggest when I have time to adjust the them to reflect it. There’s work being done with the rxjs TestScheduler as well in the next month or so.

Hi, just found this thread after a long debugging session. The custom mockStore function above works. Just so I understand, is this a suggested temporary workaround, and if so, what will the long-term solution look like?

const mockStore = (...args) => {
      const epicMiddleware = createEpicMiddleware(someEpic);
      const factory = configureMockStore([epicMiddleware]);
      return factory(...args);
};

This saved my day. I have been stuck whole day trying to figure out what was wrong with my redux store configuration. Thanks @jayphelps

With v1.x of this lib and v6.x of rxjs, the above workaround is now outdated. createEpicMiddleware no longer takes epic arguments as input and you must call epicMiddleware.run with those epic arguments after the store is instantiated. An updated version of this workaround looks like:

let epicMiddleware;
const mockStore = (...args) => {
    epicMiddleware = createEpicMiddleware();
    const factory = configureMockStore([myEpic]);
    return factory(...args);
};

... // in my spec
const store = mockStore(initialState);
epicMiddleware.run(myEpic)

Less intuitive than before, and more repetitive 😕

🎉 Happy New Year from France by the way 😃 🎉