Testing react-intl with jest and enzyme
If you don’t know react-intl, it is an i18n library for React. It is pretty straightforward to use as part of your react project.
Yet it seems to be, that testing a component wrapped with the ‘injectIntl’ high order component, is not as straightforward.
I’ve been struggling with doing so, for a few hours by now, and I’d like to make your life easier 😛
If you don’t know one or more of the libraries, I mentioned in the title, I suggest you Google it and take the time to know them because it is essential to this post.
Let’s code:
My app is pretty simple (It is only a demo), it is structured of App.js
and SubComp.js
// App.jsimport React, { Component } from 'react';
import { IntlProvider } from 'react-intl';
import SubComp from '../SubComp'
import './App.css';const messages = {OK: 'ALL IS OK!'};class App extends Component {
render () {
return (
<IntlProvider locale=’en' messages={messages}>
<div className="App">
<h1>This is App</h1>
<SubComp />
</div>
</IntlProvider>
);
}
}export default App;//SubComp.js
import React, { Component } from 'react';
import { injectIntl } from 'react-intl';
import SubComp1 from '../SubComp.1';class SubComp extends Component {
render() {
return (
<div>
<h1>This is SubComp</h1>
<p>{this.props.intl.formatMessage( { id: 'OK' } )}</p>
</div>
);
}
}export default injectIntl(SubComp);
Now for the important stuff.
TESTING!
What I’d like to test is <SubComp />
.
My test is as simple as it can get.
describe( 'SubComp', () => {
it( 'Should render', () => {
const wrapper = shallow( <SubComp /> )
expect( wrapper.find( 'p' ).length ).toBe(1)
});
});
You might won’t believe it, it FAILS.
I actually got this next error in my console:
Invariant Violation: [React Intl] Could not find required 'intl' object. <IntlProvider> needs to exist in the component ancestry.
So after a little Google, I found enzyme-react-intl. It gives almost the same options as enzyme, with little naming differences.
shallow will become shallowWithIntl
mount will become mountWithIntl… and so on.
The test is still the same … and the result is still the same. it FAILS
What the F***
I’ve Googled it! It must be right. Well it is.
I found that the problem was I needed to export <SubComp />
as a named export and not only as default export.
export class SubComp extends Component { ...
And … IT WORKS! 👏
But wait! I’d like to test, not only that I have a <p>, but also that it’s content is equal to what I pass it. In other words, I’d like to see that
this.props.intl.formatMessage( { id: ‘OK’ } ) will result “ALL IS OK!”
In order to do so, I’ve replaced enzyme-react-intl with my own implementation (I found on … Google!. Just changed it a little)
import React from 'react';
import { mount, shallow } from 'enzyme';
import { IntlProvider, intlShape } from "react-intl";// Create IntlProvider to retrieve React Intl context
export const initIntlProvider = ( locale = 'en', messages = {} ) =>
{
const intlProvider = new IntlProvider({locale,messages},{});
const { intl } = intlProvider.getChildContext();
return intl;
}// `intl` prop is required when using injectIntl HOC
const nodeWithIntlProp = (node, intl) => (
React.cloneElement( node, { intl })
);// shallow() with React Intl context
export const shallowWithIntl = (
node, intl, { context, ...options } = {} ) => {
return shallow(
nodeWithIntlProp( node, intl ),
{...options,
context: {...context, intl }
});
};// mount() with React Intl context
export const mountWithIntl = (
node, intl,{ context, childContextTypes, ...options } = {}) => {
return mount(
nodeWithIntlProp( node, intl ),
{...options,
context: {...context, intl },
childContextTypes: {
intl: intlShape,
...childContextTypes
}
}
);
};
So, what i’ve changed? I have added the ‘initIntlProvider’ function and I pass the ‘intl’ object between functions. WHY? because, it enables me to pass in the locale and it’s messages. Now I can test something like
expect( wrapper.find( 'p' ).text() ).toEqual( 'This is All!' )
Well that is it. This post got out a little longer than I expected, but I hope it helped you.