I'm trying to verify with a test that a stateful component's state is appropriately changed in componentDidMount
, but hit a wall due to react-router.
I'm using Enzyme, so I use mount
in order to evaluate lifecycle methods such as componentDidMount
. Typically, this works just 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's myProperty value
// from "initial value" to "new value"
expect(wrapper.instance().state.myProperty).toEqual(newValue);
});
...but the component in question is problematic because mount
renders a couple children deep, and in this case one of those descendants uses react-router's <Link>
. So, running the above test results in errors: TypeError: Cannot read property 'history' of undefined
and Failed context type: The context `router` is marked as required in `Link`, but its value is `undefined`.
The react-router docs recommend surrounding a test render of a component that requires context (for example, uses react-router's <Link>
) with either <MemoryRouter>
or <StaticRouter>
, but that won't work because that makes the component under test a child instead of the root of the ReactWrapper, which makes it impossible (as far as I know) to retrieve 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 fails with error ReactWrapper::instance() can only be called on the root
).
I soon learned that enzyme's mount
takes an options argument that allows for context to be passed into the render, which is what react-router needs. So I tried removing the router containment and providing 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 that results in the same errors about context type that I began with. Either I'm not passing context in correctly, I don't know how to get the context carried down to the descendant that needs it, or there isn't a way (with these tools) to do so.
From here, I've been looking all over for details of how I might stub the context or mock one of the components, but haven't managed to put together the puzzle pieces effectively enough to successfully write and run this test.
How can I validate a component's state as changed by componentDidMount
when it has descendants that depend on a context that satisfies react-router modules?