Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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'
}]);
like image 342
Salman Avatar asked Oct 04 '15 21:10

Salman


People also ask

What happens when you dispatch an action Redux?

The app code dispatches an action to the Redux store, like dispatch({type: 'counter/incremented'}) The store runs the reducer function again with the previous state and the current action , and saves the return value as the new state.

How is state updated in Redux applications?

The store is a “container” (really a JavaScript object) that holds the application state, and the only way the state can change is through actions dispatched to the store. Redux allows individual components connect to the store and apply changes to it by dispatching actions.

Can I dispatch an action in reducer?

Dispatching an action within a reducer is an anti-pattern. Your reducer should be without side effects, simply digesting the action payload and returning a new state object. Adding listeners and dispatching actions within the reducer can lead to chained actions and other side effects.


1 Answers

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
});
like image 58
lukewestby Avatar answered Sep 23 '22 05:09

lukewestby