Redux How to update the store in unit tests?

24,641

Solution 1

configureMockStore is a factory that is used to configure a mock store by applying the specified middlewares. This factory returns a mockStore function.

The mockStore function itself returns an instance of the configured mock store. It doesn't change state through actions; instead it just records which actions were passed. This is because it's a utility tool to create unit tests and not "integration" (state + component) tests.

Nonetheless, you can simulate a state change. mockStore accepts a function, so you could do the following:

import configureMockStore from 'redux-mock-store';

const middlewares = [];
const mockStore = configureMockStore(middlewares);

let state = {
  players: {
    'audio-player-1': { paused: false }
  }
};

const store = mockStore(() => state);

Then in your tests, you can do:

state = NEW_STATE;

// now you need to make the components update the state.
// so you can dispatch any action so the mock store will notify the subscribers
store.dispatch({ type: 'ANY_ACTION' }); 

Solution 2

What you can do is use a real store in your test. First, create a reducer function:

const reducer = (state, action) => {
  if (action.type === actionTypes.player.UPDATE_OPTION) {
    return {
      ...state,
      players: {
        ...state.players,
        [action.id]: {
          ...state.players[action.id],
          [action.key]: action.value,
        },
      },
    };
  }
  return state;
};

(Note, if you're ok with not preserving other state in this test you could simplify the above and just return a new state.)

Then create a store with that reducer and the initial state:

import { createStore } from 'redux';

const store = createStore(reducer, {
  players: {
    'audio-player-1': { paused: false }
  }
});

With that, your dispatch with updateOption should result in the new state.

Share:
24,641

Related videos on Youtube

Martin Dawson
Author by

Martin Dawson

Want automated Discounted Cash Flows? Check out https://tracktak.com

Updated on November 29, 2020

Comments

  • Martin Dawson
    Martin Dawson over 3 years

    Using enzyme, mocha and expect asserts.

    The aim of my unit test is to check that dispatch gets called with the correct arguments when paused and not paused in mergeProps. I need to dynamically change the state of my store to do: paused: true.

    At the moment I try and update the paused value by dispatching but I don't think this is correct because it's just a mock and never actually runs through the reducer.

    I am using the package redux-mock-store.

    How do I do this?

    describe('Play Container', () => {
      const id = 'audio-player-1';
    
      const store = configureMockStore()({
        players: {
            'audio-player-1': { paused: false }
        }
      });
      let dispatchSpy;
      let wrapper;
    
      beforeEach(() => {
        dispatchSpy = expect.spyOn(store, 'dispatch');
        wrapper = shallow(
          <PlayContainer className={attributes.className}>
            {children}
          </PlayContainer>,
          { context: { id } },
          ).shallow({ context: { store } });
      });
    
      it('onClick toggles play if paused', () => {
        //Not Working
        store.dispatch(updateOption('paused', true, id));
        wrapper.simulate('click');
        expect(dispatchSpy).toHaveBeenCalledWith(play(id));
      });
    
      it('onClick toggles pause if playing', () => {
        wrapper.simulate('click');
        expect(dispatchSpy).toHaveBeenCalledWith(pause(id));
      });
    });
    

    container:

    const mapStateToProps = ({ players }, { id }) => ({
      paused: players[id].paused
    });
    
    const mergeProps = (stateProps, { dispatch }, { id }) => ({
      onClick: () => (stateProps.paused ? dispatch(play(id)) : dispatch(pause(id)))
    });
    
    export default connectWithId(mapStateToProps, null, mergeProps)(Play);
    

    connectWithId:

    //getContext() is from recompose library and just injects id into props
    export const connectWithId = (...args) => compose(
      getContext({ id: React.PropTypes.string }),
      connect(...args),
    );
    

    actions:

    updateOption: (key, value, id) => ({
        type: actionTypes.player.UPDATE_OPTION,
        key,
        value,
        id,
    }),
    
    • denvaar
      denvaar over 7 years
    • Luis Crespo
      Luis Crespo about 7 years
      Yep I also suggest reading the above docs. You should test three different things separately: actions, reducers, components. When testing actions (actually action creators), you just want to make sure that the actual action dispatched is formed correctly. When testing reducers, you'd like to mock the store as you're doing already, dispatch some action, and make sure the new state returned is what you expect. When testing components, you'd like to actually test behavior (i.e., if I click here then dispatch is called with that), mocking dispatch of course.
  • Vipul Dessai
    Vipul Dessai over 3 years
    initializing the mock store with a function returning the mock state is very important as mentioned above, const store = mockStore(() => state); and also the state needs to be overwritten, just changing the property won't work