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
.
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
?
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