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

Enzyme 3 couldn't test component when ComponentDidCatch happend #1255

Closed
Tracked by #1553
BlackGanglion opened this issue Oct 12, 2017 · 17 comments
Closed
Tracked by #1553

Enzyme 3 couldn't test component when ComponentDidCatch happend #1255

BlackGanglion opened this issue Oct 12, 2017 · 17 comments

Comments

@BlackGanglion
Copy link

My Component:

class Error extends React.Component {
  render() {
    throw new Error('ErrorMessage');
    return <div />;
  }
}

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null
    }
  }
  componentDidCatch(error, info) {
    this.setState({
      hasError: true,
      error,
      errorInfo: info
    });
  }
  render() {
    const { hasError } = this.state;
    if (this.state.hasError) {
      return <div className="error-view">error view</div>
    }
    return <Error />
  }
}

My test:

const app = mount(<App />);
const errorTip = app.setState({
  hasError: true,
});
expect(app.find('.error-view').length).to.equal(1);

When app is mount,app throws the error and I couldn't get the app to test the error view. How do I solve it?

@nickytonline
Copy link

@nickytonline
Copy link

I've also tried my errored component as a class instead of an SFC,

class ErroredComponent extends React.Component {
  render() {
    throw new Error('Oh no!');
  }
}

but it results in the same thing.

@trevtrich
Copy link

Any suggested best practice on this? Has anyone found a solution they're happy with? I'm working on a similar problem.

@pizzarob
Copy link

You can test componentDidCatch using a spy method. I am using expect.spyOn there's also sinon. Here's is a working example of testing componentDidCatch:

import Login from './index';
import expect from 'expect';
import React from 'react';
import { mount } from 'utils/enzyme';

function ProblemChild() {
  throw new Error('Error thrown from problem child');
  return <div>Error</div>; // eslint-disable-line
}

describe('<Login />', () => {
  it('should catch errors with componentDidCatch', () => {
    const spy = expect.spyOn(Login.prototype, 'componentDidCatch');
    mount(<Login><ProblemChild /></Login>);
    expect(Login.prototype.componentDidCatch).toHaveBeenCalled();
  });
});

@QuentinRoy
Copy link

QuentinRoy commented Dec 19, 2017

@realseanp Any idea how to remove the annoying console logging? I was able to mute react's by mocking global.console but couldn't mute jsdom's...

@craigkovatch
Copy link

Possibly related issue on Jasmine: jasmine/jasmine#1453

IMO Jasmine should probably be agnostic of the infrastructure to properly catch errors thrown by React components. Enzyme seems like the more natural place to solve this.

@gricard
Copy link

gricard commented Jan 12, 2018

@realseanp Nice. I did the same with jest.spyOn() and that got my coverage up to 100%. Thank you!

@brucewpaul
Copy link
Contributor

This workaround only tests to make sure that componentDidCatch, which is definitely better then nothing. Ideally enzyme should also be able to test that the rendered output if there is an error, and ensure that it was rendered correctly as well.

@ljharb is there a milestone/checklist anywhere of react features not yet supported?

@ljharb ljharb mentioned this issue Mar 2, 2018
41 tasks
@ljharb
Copy link
Member

ljharb commented Mar 2, 2018

@brucewpaul filed #1553 to track everything.

@jessicarobins
Copy link

@QuentinRoy I had the same issue and was able to mute jsdom's console by doing:
jest.spyOn(window._virtualConsole, 'emit').mockImplementation(() => false);
in a beforeEach. That might be a terrible idea? But it doesn't seem to have broken anything...

@mrlubos
Copy link

mrlubos commented Apr 2, 2018

@jessicarobins That's a good tip! For others reading this, if you want to mute all error output, silence console.error as that's the function used by jsdom and doesn't rely on jsdom interface which could change.

@lesbaa
Copy link

lesbaa commented May 11, 2018

I'd argue that muting errors isn't necessarily the best course of action, error messages are there for a reason, no?

@QuentinRoy
Copy link

@lesbaa Yes in general I entirely agree. Though in this case this is a message from React warning about the exact behaviour being under scrutiny. So it is really just confusing noise in the test output. I wish react gave a way to disable this warning so that we do not have to mute all output, but it does not seem to be a way to do that yet.

@kohlmannj
Copy link

Following up on @pizza-r0b's technique, as mentioned here:
#1255 (comment)

expect v23.5.0 does not appear to have a spyOn() method (it was donated to Jest, so that may account for the API difference?). I would like to avoid add another devDependency to my project, so I'm going to avoid sinon for now.

Instead, I've found that the following technique appears to work with expect().toThrowError() in Jest 23.5.0, Enzyme 3.4.1, and React 16.4.2.

Spoiler: the technique involves calling Enzyme's ReactWrapper.html() and expecting an error to be thrown. You can also do this with Enzyme's ShallowWrapper.html().

With mount()

// Login.test.jsx
import React from 'react';
import { mount } from 'enzyme';
import Login from './index';

function ProblemChild() {
  throw new Error('Error thrown from problem child');
  return <div>Error</div>; // eslint-disable-line
}

describe('<Login />', () => {
  it('renders an error', () => {
    const wrapper = mount(<Login><ProblemChild /></Login>);
    expect(() => { wrapper.html(); }).toThrowError('Error thrown from problem child');
  });
});

Note: you may still want to suppress console.error() output, as @jessicarobins suggested above:
#1255 (comment)

As an alternative that also avoids console.error() output, consider using shallow() and dive() with this technique:

With shallow() and dive()

// Login.test.jsx
import React from 'react';
import { shallow } from 'enzyme';
import Login from './index';

function ProblemChild() {
  throw new Error('Error thrown from problem child');
  return <div>Error</div>; // eslint-disable-line
}

describe('<Login />', () => {
  it('renders an error', () => {
    const wrapper = shallow(<Login><ProblemChild /></Login>);
    expect(() => { wrapper.dive().html(); }).toThrowError('Error thrown from problem child');
  });
});

@geminiyellow
Copy link

@kohlmannj thanks , but when i use extends and throw error in constructor,

Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.

code is here.

class ProblemChild extends PureComponent {
  constructor() {
    super();
    throw new Error('Error thrown from problem child');
  }
  render() {
    return <div>Error</div>; // eslint-disable-line
  }
}

@ljharb
Copy link
Member

ljharb commented Aug 29, 2018

When using mount, componentDidCatch should already work. When using shallow on an error boundary, nothing is being rendered that could throw, so there's no way to trigger the error.

#1797 will add proper support for this.

@geminiyellow
Copy link

🙇 thank you!

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

No branches or pull requests