When calling wrapper.instance() after wrapper.unmount() it throws TypeError: component.getPublicInstance is not a function
The use-case for accessing the React component instance after the unmount is to check that any possible teardown in componentWillUnmount() has been successful. In my particular use-case I want to check that an RxJS Subscription has been unsubscribed.
Code to reproduce:
import React from 'react';
import Rx from 'rxjs';
import { mount } from 'enzyme';
class MyComponent extends React.Component {
constructor() {
super();
this.state = {
foo: 'initial'
};
}
componentDidMount() {
this.subscription = this.props.subject.subscribe(
value => this.setState({ foo: value })
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return <div>{ this.state.foo }</div>;
}
}
it('unsubscribes on unmount', () => {
const subject = new Rx.BehaviorSubject('new value');
const wrapper = mount(
<MyComponent subject={subject} />,
);
expect(wrapper.instance()).toHaveProperty('subscription.closed', false);
wrapper.unmount();
expect(wrapper.instance()).toHaveProperty('subscription.closed', true);
});
Test output:
FAIL src/ui/memo/__tests__/AfterUnmount.test.js
โ unsubscribes on unmount
TypeError: component.getPublicInstance is not a function
at WrapperComponent.getInstance (node_modules/enzyme/build/ReactWrapperComponent.js:81:32)
at ReactWrapper.instance (node_modules/enzyme/build/ReactWrapper.js:219:31)
at Object.<anonymous>.it (src/ui/memo/__tests__/AfterUnmount.test.js:37:18)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
at process._tickCallback (internal/process/next_tick.js:103:7)
There is a workaround by using wrapper.node instead of wrapper.instance() in the last expect: expect(wrapper.node).toHaveProperty('subscription.closed', true);. But this feels dirty because .node is an undocumented feature.
Used versions:
enzyme: 2.8.2
jest: 20.0.3
react: 15.5.4
react-scripts: 1.0.5
rxjs: 5.4.0
enzyme-to-json: 1.5.1 (I don't think this affects this particular test, but I had it installed anyway)
const instance = wrapper.instance(); and then refer to instance after that?
Yeah, that's a nice workaround, thanks!
I think it's still a bug, though. I don't know the internals, but clearly the instance still exists after unmount() because we can access it via reference. So, it's unintuitive that we can't access it via the instance().
This could be either fixed so that instance() still works after unmount() or at least make the documentation more clear when you can and when you cannot call instance().
Fair points.
I think it's worth ensuring instance still works after unmount.
Does this only happen on mount, or also on shallow?
With shallow() it works fine :+1:
Here is still a minimal snippet to reproduce the original problem with mount():
const wrapper = mount(<div></div>);
wrapper.unmount();
wrapper.instance();
I could try to take a crack at this.
I took a shot at this in #969
The issue was that unmount causes the ReactWrapperComponent render to return null which renders an empty component. Apparently empty components don't have the .getPublicInstance() method that was being used in .getInstance() so it threw a TypeError. I figured the easiest/least-invasive way to still get at it was to store a reference to the last render on the components state.
Most helpful comment
const instance = wrapper.instance();and then refer toinstanceafter that?