Testing state change with React, reactive router, joke and enzyme
I am trying to verify with a test that the state of the state with the set state is appropriately changed in componentDidMount
but hit the wall due to react-router.
I am using Enzyme, so I am using mount
lifecycle methods like componentDidMount
. This usually works fine ...
it("changes state after mount", () => {
const newValue = "new value";
const testPropertyRetriever = () => newValue;
const wrapper = mount(
<StatefulPage
myProperty="initial value"
propertyRetriever={testPropertyRetriever}
/>
);
// componentDidMount should have executed and changed the state myProperty value
// from "initial value" to "new value"
expect(wrapper.instance().state.myProperty).toEqual(newValue);
});
... but this component is problematic because it mount
makes a couple of children deep, in which case one of those children is using react-router <Link>
. Thus, when executing the above test results, errors occur: TypeError: Cannot read property 'history' of undefined
andFailed context type: The context `router` is marked as required in `Link`, but its value is `undefined`.
The answer-router docs recommend that you surround test rendering of a component that requires context (for example, uses react-router <Link>
) with <MemoryRouter>
or <StaticRouter>
, but that won't work because that makes the component under test a child and not the root of the ReactWrapper, which makes it impossible (as far as I know ) getting the state of the component under test. (Given the example above ...
// ...
const wrapper = mount(
<MemoryRouter>
<StatefulPage
myProperty="initial value"
propertyRetriever={testPropertyRetriever}
/>
</MemoryRouter>
);
expect(wrapper.childAt(0).instance().state.myProperty).toEqual(newValue);
... the test failed ReactWrapper::instance() can only be called on the root
).
I soon learned that the enzyme mount
takes an options argument, which allows the context to be passed to the render, which is what the router needs to react to. So I tried to remove the router wrapper and provide context (based on this answer ) ...
//...
const wrapper = mount(
<StatefulPage
myProperty="initial value"
propertyRetriever={testPropertyRetriever}
/>,
{ router: { isActive: true } }
);
expect(wrapper.instance().state.myProperty).toEqual(newValue);
... but this leads to the same errors regarding the type of context I started with. Either I am not passing the context correctly, I donβt know how to get the context to carry over to the descendant it needs, or thereβs no way (with these tools) to do it.
Then I went into detail about how I can drown out the context or make fun of one of the components, but have not managed to piece the puzzle together efficiently enough to successfully write and run this test.
How can I check the state of a component as changed to componentDidMount
when it has descendants that depend on a context that satisfies react-router modules?
source to share
The router definition provided by the mount function was incomplete.
const MountOptions = {
context: {
router: {
history: {
createHref: (a, b) => {
},
push: () => {
},
replace: () => {
}
}
}
}, childContextTypes: {
router: PropTypes.object
}
};
const wrapper = mount(
<StatefulPage
myProperty="initial value"
propertyRetriever={testPropertyRetriever}
/>,
MountOptions
);
source to share