EDIT: Solved! Scroll down for the answer
In our Component tests we need them to have access to the react-intl
context. The problem is that we are mounting single components (with Enzyme's mount()
) without their <IntlProvider />
parent wrapper. This is solved by wrapping the provider around but then the root
points to the IntlProvider
instance and not to CustomComponent
.
The Testing with React-Intl: Enzyme docs are still empty.
<CustomComponent />
class CustomComponent extends Component { state = { foo: 'bar' } render() { return ( <div> <FormattedMessage id="world.hello" defaultMessage="Hello World!" /> </div> ); } }
Standard Test Case (Desired) (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');
However, since our component uses FormattedMessage
as part of the react-intl
library, we get this error when running the above code:
Uncaught Invariant Violation: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.
Wrapping it with IntlProvider
const wrapper = mount( <IntlProvider locale="en"> <CustomComponent params={params} /> </IntlProvider> );
This provides CustomComponent
with the intl
context it asks for. However, when trying to do test assertions such as these:
expect( wrapper.state('foo') ).to.equal('bar');
raises the following exception:
AssertionError: expected undefined to equal ''
This ofcourse because it tries to read the state of IntlProvider
and not our CustomComponent
.
CustomComponent
I have tried the below to no avail:
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
The question is: How can we mount CustomComponent
with the intl
context while still being able to perform "root" operations on our CustomComponent
?
mount method renders the full DOM including the child components of the parent component that we are running the tests. This is more suitable when there are components which directly interfere with DOM API or lifecycle methods of React.
injectIntl HOC This function is exported by the react-intl package and is a High-Order Component (HOC) factory. It will wrap the passed-in React component with another React component which provides the imperative formatting API into the wrapped component via its props .
Many people choose to use Jest and Enzyme together to test their React web applications. They use Jest as a test runner and assertion library, then use Enzyme to build the tests for their UI. This results in slimmer, cleaner testing code that's also easier to debug when a test breaks.
I have created a helper functions to patch the existing Enzyme mount()
and shallow()
function. We are now using these helper methods in all our tests where we use React Intl components.
You can find the gist here: https://gist.github.com/mirague/c05f4da0d781a9b339b501f1d5d33c37
For the sake of keeping data accessible, here's the code in a nutshell:
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 } }); } };
CustomComponent
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
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