Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrapper.instance() throws error after wrapper.unmount() #956

Open
apaatsio opened this issue May 25, 2017 · 6 comments · May be fixed by #969
Open

wrapper.instance() throws error after wrapper.unmount() #956

apaatsio opened this issue May 25, 2017 · 6 comments · May be fixed by #969

Comments

@apaatsio
Copy link

apaatsio commented May 25, 2017

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)

@ljharb
Copy link
Member

ljharb commented May 25, 2017

const instance = wrapper.instance(); and then refer to instance after that?

@apaatsio
Copy link
Author

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().

@ljharb
Copy link
Member

ljharb commented May 26, 2017

Fair points.

I think it's worth ensuring instance still works after unmount.

Does this only happen on mount, or also on shallow?

@apaatsio
Copy link
Author

With shallow() it works fine 👍

Here is still a minimal snippet to reproduce the original problem with mount():

const wrapper = mount(<div></div>);
wrapper.unmount();
wrapper.instance();

@kwngo
Copy link

kwngo commented Jun 2, 2017

I could try to take a crack at this.

@aaron-j-krause
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants