alengel
alengel

Reputation: 4958

React Warning: Failed Context Types: Required context `router` was not specified in `Component`

I'm trying to test a React component that requires the react-router in separately from app.js.

I have a component that does a redirect using the mixin Router.Navigation like so:

var React = require('react'),
    Router = require('react-router');

var Searchbar = React.createClass({

  mixins : [Router.Navigation],

  searchTerm: function(e) {
    if (e.keyCode !== 13) {
      return;
    }

    this.context.router.transitionTo('/someRoute/search?term=' + e.currentTarget.value)
  },

  render: function() {
    return (
      <div className="searchbar-container">
        <input type="search" placeholder="Search..." onKeyDown={this.searchTerm} />
      </div>
    )
  }
});

module.exports = Searchbar;

I tried to write a test for this but ran into a wall. Apart from the fact that I'm unable to test that transitionTo works as expected, I've also encountered this error message in my Jest tests:

Warning: Failed Context Types: Required context router was not specified in Searchbar.

Does anyone know how I can get rid of the warning and bonus question, how I can test that the transition works as expected?

I've done research into this and this conversation on Github here: https://github.com/rackt/react-router/issues/400 is the closest I've found to the problem. It looks like I need to export the router separately but that seems like a lot of overhead to just run component tests without the warning a la https://github.com/rackt/react-router/blob/master/docs/guides/testing.md

Is that really the way to go?

Upvotes: 10

Views: 8897

Answers (4)

D0m3
D0m3

Reputation: 1501

My answer is not Jest-specific but it might help people coming across the same problem. I created a class to wrap router context.

Then in your test just add
<ContextWrapper><YourComponent/></ContextWrapper>

It can be useful to wrap other things like ReactIntl.

Note that you will lose the possibility to use shallow rendering but that's already the case with ReactIntl.

Hope that helps someone.


ContextWrapper.js

import React from 'react';

export default React.createClass({
    childContextTypes: {
        router: React.PropTypes.object
    },

    getChildContext () {
        return {
            router: {}
        };
    },

    render () {
        return this.props.children;
    }
});

Upvotes: 2

alengel
alengel

Reputation: 4958

Here is my Jest file for a complete answer to this question. BinaryMuse’s last paragraph got me on the right track but I find code examples always the most helpful, so here it is for future reference.

jest.dontMock('./searchbar');

describe('Searchbar', function() {
  var React = require('react/addons'),
      Searchbar = require('../../components/header/searchbar'),
      TestUtils = React.addons.TestUtils;

  describe('render', function() {
    var searchbar;

    beforeEach(function() {
      Searchbar.contextTypes = {
        router: function() {
          return {
            transitionTo: jest.genMockFunction()
          };
        }
      };

      searchbar = TestUtils.renderIntoDocument(
        <Searchbar />
      );
    });

    it('should render the searchbar input', function() {
      var searchbarContainer = TestUtils.findRenderedDOMComponentWithClass(searchbar, 'searchbar-container');

      expect(searchbarContainer).toBeDefined();
      expect(searchbarContainer.props.children.type).toEqual('input');
    });

  });

});

Hope this helps someone else in the future.

Upvotes: 3

Yevgen Safronov
Yevgen Safronov

Reputation: 4033

Because the router relies heavily on the lesser known context feature of React you need to stub it like described here

var stubRouterContext = (Component, props, stubs) => {
  return React.createClass({
    childContextTypes: {
      makePath: func,
      makeHref: func,
      transitionTo: func,
      replaceWith: func,
      goBack: func,
      getCurrentPath: func,
      getCurrentRoutes: func,
      getCurrentPathname: func,
      getCurrentParams: func,
      getCurrentQuery: func,
      isActive: func,
    },

    getChildContext () {
      return Object.assign({
        makePath () {},
        makeHref () {},
        transitionTo () {},
        replaceWith () {},
        goBack () {},
        getCurrentPath () {},
        getCurrentRoutes () {},
        getCurrentPathname () {},
        getCurrentParams () {},
        getCurrentQuery () {},
        isActive () {},
      }, stubs);
    },

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

And use like:

var stubRouterContext = require('./stubRouterContext');
var IndividualComponent = require('./IndividualComponent');
var Subject = stubRouterContext(IndividualComponent, {someProp: 'foo'});
React.render(<Subject/>, testElement);

Upvotes: 4

Michelle Tilley
Michelle Tilley

Reputation: 159105

In version 0.13 of React Router, the mixins Navigation and State were deprecated. Instead, the methods they provide exist on the object this.context.router. The methods are no longer deprecated, but if you're using this.context.router explicitly you don't need the mixin (but you need to declare the contextTypes directly); or, you can use the mixin, but don't need to use this.context.router directly. The mixin methods will access it for you.

In either case, unless you render your component via React Router (via Router#run), the router object is not supplied to the context, and of course you cannot call the transition method. That's what the warning is telling you—your component expects the router to be passed to it, but it can't find it.

To test this in isolation (without creating a router object or running the component through Router#run), you could place a mocked router object on the component's context in the correct place, and test that you call transitionTo on it with the correct value.

Upvotes: 5

Related Questions