I have been trying (without success) to write a test case for ErrorBoundary
component that is handling errors via componentDidCatch lifecycle method. Despite the error produced by child component inside the <ErrorBoundry>
component, <ErrorBoundry>
does not render info about error in code but the content of faulty component if it would work correct. Component works as expected in production/development but not when it is executed by Jest / Enzyme for testing.
Error from testing:
PASS src/ErrorBoundary.test.js ● Console console.error node_modules/fbjs/lib/warning.js:33 Warning: `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components. in input (at ErrorBoundary.test.js:11) in div (at ErrorBoundary.test.js:10) in ComponentWithError (at ErrorBoundary.test.js:26) in ErrorBoundry (created by WrapperComponent) in WrapperComponent console.log src/ErrorBoundary.test.js:29 <ErrorBoundry> <ComponentWithError> <div> <input type="text" value={{...}} /> </div> </ComponentWithError> </ErrorBoundry>
ErrorBoundry.js:
import React, { Component } from 'react' import Raven from 'raven-js' import { Segment, Button } from 'semantic-ui-react' export default class ErrorBoundry extends Component { state = { hasError: false } componentDidCatch(error, info) { this.setState({ hasError: true }) Raven.captureException(error, { extra: info }); } render() { if(this.state.hasError) { return ( <div className='error-boundry'> <Segment> <h2> Oh no! Somethin went wrong </h2> <p>Our team has been notified, but click <Button onClick={() => Raven.lastEventId() && Raven.showReportDialog()}> here </Button> to fill out a report. </p> </Segment> </div> ); } else { return this.props.children; } } }
ErrorBoundry.test.js:
import React, { Component } from 'react' import ReactDOM from 'react-dom' import renderer from 'react-test-renderer' import { shallow, mount } from 'enzyme' import ErrorBoundary from './ErrorBoundary' class ComponentWithError extends Component { render() { return ( <div> <input type = "text" value = {null}/> </div> ); } } describe('<ErrorBoundary> window',()=> { it('should match the snapshot', () => { const tree = renderer.create(<ErrorBoundary>Test</ErrorBoundary> ).toJSON() expect(tree).toMatchSnapshot() }) it('displays error message on error generated by child', () => { const wrapper = mount( <ErrorBoundary > <ComponentWithError /> </ErrorBoundary> ) console.log(wrapper.debug() ) }) })
With the new feature in React, developers can test the Error Boundaries by a toggle error button, added to the DevTools. When we click on the toggle error button on the component labeled 'Inner Component', Error boundary is triggered. We see the An error was thrown text.
Enzyme has simulateError
helper now.
So this works very well for me:
const Something = () => null; describe('ErrorBoundary', () => { it('should display an ErrorMessage if wrapped component throws', () => { const wrapper = mount( <ErrorBoundary> <Something /> </ErrorBoundary> ); const error = new Error('test'); wrapper.find(Something).simulateError(error); /* The rest fo your test */ } }
After additional research I found that it is an open issue that has to be solved by Enzyme. https://github.com/airbnb/enzyme/issues/1255
I have implemented it as follows:
function ProblemChild() { throw new Error('Error thrown from problem child'); return <div>Error</div>; // eslint-disable-line } describe('<ErrorBoundary> window',()=> { it('displays error message on error generated by child', () => { const spy = sinon.spy(ErrorBoundary.prototype, 'componentDidCatch') mount(<ErrorBoundary><ProblemChild /></ErrorBoundary>) chaiExpect(ErrorBoundary.prototype.componentDidCatch).to.have.property('callCount', 1) }) })
Proposed workaround works anyhow
<ErrorBoundary>
test console displays warnings:
PASS src/ErrorBoundary.test.js
● Console
console.error node_modules/react-dom/cjs/react-dom.development.js:9627 The above error occurred in the <ProblemChild> component: in ProblemChild (at ErrorBoundary.test.js:37) in ErrorBoundry (created by WrapperComponent) in WrapperComponent React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundry.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With