Salman
Salman

Reputation: 9447

dispatch(action) doesn't immediately update redux state in unit tests

I am writing test cases for my application bases on react and redux.

container = TestUtils.renderIntoDocument(
  <Provider store={createStore({"key": "old_val"})}>
    {() => <Component />}
  </Provider>
);

After rendering with initialState, I dispatch an action and see if the state changes.

Component.store.dispatch({ type: 'SET_VAL', value: 'some_val' });

Then I print the state

console.log(store.getState());

I expect the state to be {"key": "some_val"}. However, it still shows {"key": "old_val"}.

The application works fine, just not the test, so there can't be any problem with my action-creators or reducers.

Am I doing something wrong here? Btw, I am using thunk middleware for async action dispatch. Does that interfere here? If yes, how do I wait till the async action gets completed?

Update:

The redux tests shown here are pretty straightforward, yet they seem to work fine.

store.dispatch(addTodo('Hello'));
expect(store.getState()).toEqual([{
  id: 1,
  text: 'Hello'
}]);

Upvotes: 8

Views: 5680

Answers (1)

lukewestby
lukewestby

Reputation: 1207

One of the huge benefits of redux is that it allows you to implement almost all of your application using pure functions and pure components. Redux and react-redux abstract the implementation details of subscribing UI to state changes which allows you to test all of your app's code in isolation. This way you don't need to render out a provider with a store every time you want to test out your code, which is a major reduction in complexity.

Let's say you have a key property in your state and a KeyDisplay component. You can implement the state with the following reducer file:

reducers.js

import { combineReducers } from 'redux';

export function key(state, { type, value }) {
  switch(type) {
    case 'SET_VAL': return value;
    default: return state;
  }
}

export default combineReducers({ key });

And you can set up a file for our component:

KeyDisplay.js

import React from 'react';
import { connect } from 'react-redux';

export function KeyDisplay({ keyProp }) {
  return (
    <div>The key is {keyProp}</div>
  );
}

export default connect((state) => { keyProp: state.key })(KeyDisplay);

Then in the unit test for the reduce you can import just the reducer for key and test it totally separate of the user interface:

keyReducer.test.js

import test from 'tape';
import { key } from './reducers.js';

test('key reducer', (t) => {
  t.plan(1);
  const output = key('old', { type: 'SET_VAL', value: 'new' });
  t.equal(output, 'new', 'SET_VAL should override old value');
});

Additionally, since connect passes state as props into the component you can just render the unconnected component with some test props that represent a state you are interested in, again without setting up a store and provider:

KeyDisplay.test.js

import test from 'tape';
import { renderIntoDocument } from 'react-addons-test-utils';
import { KeyDisplay } from './KeyDisplay.js';

test('KeyDisplay', (t) => {
  const component = renderIntoDocument(<KeyDisplay keyProp="test" />);
  // test component
});

Upvotes: 7

Related Questions