web-dev-qa-db-fra.com

Comment écrire un cas de test pour ErrorBoundary dans React using Jest / Enzyme

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() )
  })
})
9
Sylwek

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

  1. il n'est toujours pas possible de tester le message d'erreur rendu à l'utilisateur de l'application par <ErrorBoundary>
  2. 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.
    
5
Sylwek

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 */
  }
}
5
Daniil Ivanov

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()
  });
0
dcodesmith