reduxjs/redux-mock-store

Mock Store State is not being updated

adamyonk opened this issue ยท 18 comments

I'm having the same issue as #45. Maybe I'm not understanding this correctly, but does mock-store actually run the actions through the reducers anywhere? I'm not seeing how they're ever connected with a setup like this:

import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const mockStore = configureStore([thunk])
const store = mockStore({ ...state })
console.log(store.getState()) // => { ...state }
store.dispatch(someAction())
console.log(store.getActions()) // => [ someAction() ] The action does show up here!
console.log(store.getState()) // => { ...state } But this is the same unchanged state as above

As you can see from codebase - it doesn't execute any reducers.
it even doesn't accept any of them during setup.
It only notifies the list of subscribers.
So the state will be always the same.

OK, that's what I was thinking. Is there a reason for getState in the API then?

I'm not sure, but I think the main reason is backward compatibility with redux API.
As you can see here - the value of getStore is just bypassed.

Ah, OK. So I was thinking that I'd be able to test the state along with testing what actions were triggered, but redux-mock-store is really just for the actions part. Seeing getState it in the docs threw me, thanks for clarifying!

The one place this kind of falls down is when you're testing something that uses an async action creator (thunks), that change the state before triggering other actions.

Yep, redux-mock-store is just for the testing actions part. To test reducer and it states you don't need any kind of mock. You just need to test reducer as a pure function (Input -> Output), that's all.

For testing, complex flows with async actions you can check https://github.com/redux-things/redux-actions-assertions, it was designed for that kind of testing.

That is exactly what I needed. Thanks, @dmitry-zaets!

Always welcome!

God damnit, I just found this after pulling my hair for two hours. I think it should be added to the docs very explicitly that the mock stores' state is never changed, as one would assume it does when it's in the API. Reading the discussion here, it does make sense, but it's potentially a timewaster

This is definitely something that should have documented in README.md.

@dmitry-zaets Can I document it and make a pull request not to confuse the newbie here?

@ohtangza Sure, would be happy to merge the PR

Testing your reducer alongside your actions with redux-mock-store is as simple as running your reducer function on the state that you pass to the mock store and the action you expect:

import actions from './actions'
import reducer from './reducer'
import initialState from './initialState'

const state = { ...initialState, numbers: '23' }
const expectedAction = { type: 'DELETE_LAST_NUMBER', payload: '*' }
return store.dispatch(actions.pressKey('*')).then(() => {
  expect(store.getActions()[0]).toEqual(expectedAction)
  expect(reducer(state, expectedAction)).toEqual({ ...state, numbers: '2' })
})
tutts commented

I had the same issue, and found I kept writing the same lines over and over to test store, so wrote a tiny lib on top of redux-mock-store to capture snapshots of reducer states here

Incidentally, this can be achieved via the following:

import configureMockStore from 'redux-mock-store';
import appReducer from 'path/to/your/appReducer';

const createMockStore = configureMockStore([ ... ]);

const createState = initialState => actions => actions.reduce(appReducer, initialState);

...

it('...', () => {
  const initialState = createState({ ... } );
  const store = createMockStore(initialState);

  store.dispatch(...);

  // Assert as you prefer
  expect(store.getState()).toEqual(...);
  expect(store.getState()).toMatchObject(...);
  expect(store.getActions()).toEqual([ ... ]);
});

This is particularly useful if you're dispatching thunks which rely on getState() to control their behavior. It's also suspiciously close to just using your real store and testing dispatches against that, although you can still customize the initial state and check dispatched actions more easily.

Modified version of pzhine's answer that supports Immutable and calling the same action creator multiple times in a different action creator (we have an async action creator that we pass a synchronous action creator to).

import { fromJS } from "immutable"
import mockStore from "__tests__helpers/mockStore"

import { setTranscriptsValues } from "shared/profiles/event/event-profile-action-creators"
import { eventReducer } from "shared/profiles/event/event-profile-reducer"

let store = mockStore({})

const setTranscriptsValuesHelper = ({
    values,
    profileId,
}) => {
    store = mockStore(
        eventReducer(
            fromJS(store.getState()),
            setTranscriptsValues({
                values,
                profileId,
            })
        )
    )
}

beforeEach(() => {
    store = mockStore({})
})

it("loadInlines", () => {
    loadInlines({
 		...stuff
        setTranscriptsValues: setTranscriptsValuesHelper,
    }).then(() => {
        console.log(store.getState())
    })
})

@mbellman thanks man! It just works fine.

I finally get to test the final store state like this:

const actions = [ {type: 'action1, payload: 1}, {type: 'action2, payload: 2}]
const finalState = actions.reduce(myCoolReducer, INITIAL_STATE)
const expectedState = {...}
expect(expectedState).toEqual(finalState)

The reducer is a pure function, so in order to get the final state we just has to call every action on top of an initial state. This way every action receive the previous state and return a new one until the end of the array.