In v4 the recommended way to test Router-dependent components is to wrap them in a <MemoryRouter>. My test suite often needs a reference to the wrapped component in order to assert tests on it, for example to assert the existence of attached refs. It seems that when wrapped with <MemoryRouter> it is not possible to get a reference to the nested component.
Previously:
````javascript
let component = ReactTestUtils.renderIntoDocument(
expect(component.foo).to.exist; // ok
````
React Router v4:
````javascript
let component = ReactTestUtils.renderIntoDocument(
);
expect(component.foo).to.exist; // fail; component is MemoryRouter instance
````
I was unable to get a reference to the wrapped component by descending props.children:
javascript
console.log( component.props.children[0] ); // undefined
console.log( JSON.stringify(component.props.children) );
// '{"key":null,"ref":null,"props":{},"_owner":null,"_store":{}}'
Looking into the source of <MemoryRouter> and <Router> it seems that it may not be possible to get a reference to a wrapped component, since <Router> doesn't nest its children, and there is no tree to descend.
Stubbing out the routing context may be my only option. Is my understanding correct? Is there any other way to reference the wrapped component?
This is more of a question for how you access child elements in a tree rendered by ReactTestUtils. We're not doing anything funky other than rendering a single child element. I'm not sure of the best way to do that in RTU, so maybe you might want something like Enzyme instead.
I think that findRenderedComponentWithType() would do the trick, but I also do not use ReactTestUtils.
I tried dropping into the debugger but I was still unable to find the wrapped component. Here is a screen in chrome (I am running my tests in Karma):
Eventually I decided to mock the router context to test any components that use @withRouter.
I created this higher order component to stub the context:
````javascript
function stubRouterContext(Component) {
return class extends React.Component {
static childContextTypes = {
router: PropTypes.object.isRequired,
};
// https://github.com/ReactTraining/react-router/blob/c5c40aab197e1f8d2de118fbad1e5ac9e18d1ea9/packages/react-router/modules/Router.js#L23
getChildContext() {
return {
router: {
history: {
createHref: () => {},
push: () => {},
replace: () => {},
},
route: {
location: '/',
match: {
path: '/',
url: '/',
params: {},
isExact: true,
},
},
},
};
}
render() {
return <Component ref={C => this.WrappedComponent = C} {...this.props} />;
}
};
}
````
It's possible to access a component wrapped with @withRouter using the WrappedComponent property. This enables me to stub the router context like this:
javascript
let SidebarWithContext = stubRouterContext(Sidebar.WrappedComponent);
Then I can test my wrapped component like this:
````javascript
let { WrappedComponent } = ReactTestUtils.renderIntoDocument(
expect(WrappedComponent.foo).to.exist; // ok
````
_This is more of a question for how you access child elements in a tree rendered by ReactTestUtils...I'm not sure of the best way to do that in RTU, so maybe you might want something like Enzyme instead._
@timdorr, I don't believe this is ReactTestUtils-specific, as I am having the same issue trying to access the wrapped component using Enzyme, and it looks like @evoyy was having the same trouble with Karma.
Here is what I get back when I try and see <MemoryRouter>'s
test('it puts the lotion in the basket', () => {
const wrapper = mount(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
)
console.log(wrapper.props().children)
/* output:
{ '$$typeof': Symbol(react.element),
type: [Function: MyComponent],
key: null,
ref: null,
props: {},
_owner: null,
_store: {}
}
*/
})
This gives me access to the wrapped component's props, but I don't see any way to access the wrapped component's state, which is what I'm trying to make assertions on using Enzyme's state getter.
I posted a SO question about this same issue two weeks ago but unfortunately it hasn't received any possible solutions. I also posted the same question in the reactiflux Discord app the same day.
Just wondering if I'm missing something with how <MemoryRouter> is rendering its children on a full mount, and how I may get access to my wrapped component's state.
@indiesquidge I think I came to the conclusion that it is not possible to access a component instance which is a child of <MemoryRouter>. The only way is to not use MemoryRouter, but stub the routing context yourself for the component you want to test, using a higher order component that also stores a reference to the component as a property. I made a utility function to help with this:
````javascript
function wrapRender(Component, props) {
// stub router context (for components using @withRouter or <Link>, etc)
let ComponentWithRouter = stubRouterContext(Component);
let component = ReactTestUtils.renderIntoDocument(
<ComponentWithRouter {...props} />
);
return component.WrappedComponent;
}
````
In a test:
javascript
let component = wrapRender(Sidebar, {foo: bar});
expect(component.props.foo).to.equal("bar");
If you have the privilege of being able to upgrade to Enzyme v3 (the upgrade path is relatively easy, I recommend it if you have the bandwidth), the new API contains instance methods that allow us to get at child component state.
Thanks, Austin Wood.
It works for me:
const myWrapper = mount(<MemoryRouter><myComponent /></MemoryRouter>).find(myComponent);
const myInstance = myWrapper.instance();
Most helpful comment
Thanks, Austin Wood.
It works for me: