web-dev-qa-db-fra.com

Injection d'un objet react-intl dans des composants enzymatiques montés pour les tests

EDIT: Résolu! Faites défiler vers le bas pour la réponse


Dans nos tests de composants, nous en avons besoin pour avoir accès au contexte react-intl . Le problème est que nous montons des composants simples (avec Enzyme mount() ) sans leur enveloppe parent <IntlProvider />. Ceci est résolu en enveloppant le fournisseur, mais ensuite root pointe vers l'instance IntlProvider et non vers CustomComponent.

Les documents Test avec React-Intl: Enzyme sont toujours vides.

<Composant personnalisé />

class CustomComponent extends Component {
  state = {
    foo: 'bar'
  }

  render() {
    return (
      <div>
        <FormattedMessage id="world.hello" defaultMessage="Hello World!" />
      </div>
    );
  }
}

Cas de test standard (souhaité) (Enzyme + Mocha + Chai)

// This is how we mount components normally with Enzyme
const wrapper = mount(
  <CustomComponent
    params={params}
  />
);

expect( wrapper.state('foo') ).to.equal('bar');

Cependant, puisque notre composant utilise FormattedMessage dans le cadre de la bibliothèque react-intl, Nous obtenons cette erreur lors de l'exécution du code ci-dessus:

Uncaught Invariant Violation: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.


Envelopper avec IntlProvider

const wrapper = mount(
  <IntlProvider locale="en">
    <CustomComponent
      params={params}
    />
  </IntlProvider>
);

Ceci fournit à CustomComponent le contexte intl qu'il demande. Cependant, lorsque vous essayez de faire des affirmations de test telles que celles-ci:

expect( wrapper.state('foo') ).to.equal('bar');

lève l'exception suivante:

AssertionError: expected undefined to equal ''

Bien sûr, car il essaie de lire l'état de IntlProvider et non notre CustomComponent.


Tentatives d'accès à CustomComponent

J'ai essayé ce qui suit en vain:

const wrapper = mount(
  <IntlProvider locale="en">
    <CustomComponent
      params={params}
    />
  </IntlProvider>
);


// Below cases have all individually been tried to call `.state('foo')` on:
// expect( component.state('foo') ).to.equal('bar');

const component = wrapper.childAt(0); 
> Error: ReactWrapper::state() can only be called on the root

const component = wrapper.children();
> Error: ReactWrapper::state() can only be called on the root

const component = wrapper.children();
component.root = component;
> TypeError: Cannot read property 'getInstance' of null

La question est: Comment pouvons-nous monter CustomComponent avec le contexte intl tout en étant capable d'effectuer des opérations "root" sur notre CustomComponent?

20
Mirage

J'ai créé une fonction d'aide pour patcher la fonction Enzyme mount() et shallow() existante. Nous utilisons maintenant ces méthodes d'assistance dans tous nos tests où nous utilisons React Intl components.

Vous pouvez trouver le Gist ici: https://Gist.github.com/mirague/c05f4da0d781a9b339b501f1d5d33c37


Pour garder les données accessibles, voici le code en bref:

helpers/intl-test.js

/**
 * Components using the react-intl module require access to the intl context.
 * This is not available when mounting single components in Enzyme.
 * These helper functions aim to address that and wrap a valid,
 * English-locale intl context around them.
 */

import React from 'react';
import { IntlProvider, intlShape } from 'react-intl';
import { mount, shallow } from 'enzyme';

const messages = require('../locales/en'); // en.json
const intlProvider = new IntlProvider({ locale: 'en', messages }, {});
const { intl } = intlProvider.getChildContext();

/**
 * When using React-Intl `injectIntl` on components, props.intl is required.
 */
function nodeWithIntlProp(node) {
  return React.cloneElement(node, { intl });
}

export default {
  shallowWithIntl(node) {
    return shallow(nodeWithIntlProp(node), { context: { intl } });
  },

  mountWithIntl(node) {
    return mount(nodeWithIntlProp(node), {
      context: { intl },
      childContextTypes: { intl: intlShape }
    });
  }
};

Composant personnalisé

class CustomComponent extends Component {
  state = {
    foo: 'bar'
  }

  render() {
    return (
      <div>
        <FormattedMessage id="world.hello" defaultMessage="Hello World!" />
      </div>
    );
  }
}

CustomComponentTest.js

import { mountWithIntl } from 'helpers/intl-test';

const wrapper = mountWithIntl(
  <CustomComponent />
);

expect(wrapper.state('foo')).to.equal('bar'); // OK
expect(wrapper.text()).to.equal('Hello World!'); // OK
26
Mirage