J'ai essayé (sans succès) d'écrire un scénario de test pour le composant ErrorBoundary
qui gère les erreurs via la méthode de cycle de vie componentDidCatch. Malgré l'erreur produite par le composant enfant à l'intérieur du <ErrorBoundry>
composant, <ErrorBoundry>
ne rend pas les informations sur l'erreur dans le code mais le contenu du composant défectueux s'il fonctionne correctement. Le composant fonctionne comme prévu en production/développement, mais pas lorsqu'il est exécuté par Jest/Enzyme pour les tests.
Erreur de test:
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() )
})
})
Après des recherches supplémentaires, j'ai découvert que c'est un problème ouvert qui doit être résolu par Enzyme. https://github.com/airbnb/enzyme/issues/1255
Je l'ai implémenté comme suit:
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)
})
})
La solution de contournement proposée fonctionne de toute façon
<ErrorBoundary>
la console de test affiche des avertissements:
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.
Enzyme a simulateError
assistant maintenant.
Donc ça marche très bien pour moi:
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 */
}
}
Ajout au commentaire de @Andreas Köberle depuis que les changements d'état hasError
sur la méthode de cycle de vie ComponentDidCatch
, vous pouvez également utiliser des enzymes setState .
Vous n'avez pas non plus besoin de mount
le commentaire, shallow would do
.
it('displays error message on error generated by child', () => {
const wrapper = shallow(
<ErrorBoundary >
<ComponentWithError />
</ErrorBoundary>
);
wrapper.setState({ hasError: true });
wrapper.toMatchSnapshot()
});