Search code examples
javascriptreactjsreact-routerjestjsreactjs-testutils

nextState on componentWillUpdate not correct while testing with Jest (using also react-router wrapper)


I am using Jest 0.4.0. I have a component wrapped into this (from react-router docs):

var stubRouterContext = (Component, props, stubs) => {
  function RouterStub() { }

  Object.assign(RouterStub, {
    makePath () {},
    makeHref () {},
    transitionTo () {},
    replaceWith () {},
    goBack () {},
    getCurrentPath () {},
    getCurrentRoutes () {},
    getCurrentPathname () {},
    getCurrentParams () {},
    getCurrentQuery () {},
    isActive () {},
    getRouteAtDepth() {},
    setRouteComponentAtDepth() {}
  }, stubs)

  return React.createClass({
    childContextTypes: {
      router: React.PropTypes.func,
      routeDepth: React.PropTypes.number
    },

    getChildContext () {
      return {
        router: RouterStub,
        routeDepth: 0
      };
    },

    render () {
      return <Component {...props} />
    }
  });
};

My component uses componentWillUpdate:

  getInitialState: function(){
    return {something: ""};
  },
  componentWillUpdate: function(nextProps, nextState) {
    if(nextState.something === "12345"){
      this.context.router.transitionTo("MyRoute", {id: nextState.something});
    }
  },

In my test:

var ComponentWrapper = stubRouterContext(MyComponent, {});
var myComponentInstance = TestUtils.renderIntoDocument(<ComponentWrapper />);

it('expects to do something on componentWillUpdate', function(){
  myComponentInstance.setState({something: "12345"});
  expect(myComponentInstance.getChildContext().router.transitionTo.mock.calls[0][0]).toEqual('MyRoute'); 
  expect(myComponentInstance.getChildContext().router.transitionTo.mock.calls[0][1]).toEqual({id: '12345'});
});

As much as I call setState, my nextState in componentWillUpdate is always something: "". However, in the test, if I check the content of myComponentInstance.state then it is something: "12345". So basically, componentWillUpdate gets called but not having the new state even my instance component has it.

Any ideas on this?

--

EDIT 1

Below suggestions are based on setState being asynchronous function but that didn't solve the problem. I was also trying to simulate a store change (Flux pattern) in this way:

myStore.getState = jest.genMockFunction().mockImplementation(function() {
   return{
    something: "12345",
   };
});

myComponentInstance.onChange(); //Simulate store change (this function has setState inside taking values from the store)

Well that didn't work either, actually was telling me that onChange is not defined. So my problem is with the react-router wrapper. I found a solution but I am not sure if there are better ones cause this one looks very hacky. It is the following:

var RouterWrapper = stubRouterContext(Component, {ref: "myRealComponentInstance"});
var renderedComponent = TestUtils.renderIntoDocument(<RouterWrapper />);
var myComponentInstance = renderedComponent.refs.myRealComponentInstance;

In this way, both myComponentInstance.setState or simulating a myComponentInstance.onChange mocking the store work and I don't need to use asynchronous functions.


Solution

  • setSate is an asychronous function so you need to use the callback as shown below:

    myComponentInstance.setState({something: "12345"}, function() {
      expect(myComponentInstance.getChildContext().router.transitionTo.mock.calls[0][0]).toEqual('MyRoute');
      expect(myComponentInstance.getChildContext().router.transitionTo.mock.calls[0][1]).toEqual({
        id: '12345'
      });
    });