enzyme: Enzyme 3 couldn't test component when ComponentDidCatch happend

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?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 12
  • Comments: 17 (3 by maintainers)

Most upvoted comments

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 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…

@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…

Following up on @pizza-r0b’s technique, as mentioned here: https://github.com/airbnb/enzyme/issues/1255#issuecomment-352529487

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: https://github.com/airbnb/enzyme/issues/1255#issuecomment-377311872

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');
  });
});

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.

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?

@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 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 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
  }
}

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