ecmascript-6reduxjestjsredux-observableredux-mock-store

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


I opened a issue on but in case I'm doing something wrong :

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

Or :

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

... and so on ...

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.

Opened issues :


Solution

  • 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, I haven't checked.

    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.

    We'll track this in your ticket: https://github.com/redux-observable/redux-observable/issues/389