Unit testing React Components with Jest

André Ericson
January 31, 2017
<p><a href="https://facebook.github.io/jest/">Jest</a> is a JavaScript testing framework, it's fast and has an awesome snapshot testing feature.</p><p>This post is <strong>not an introduction to Jest</strong>, there are plenty of those around.<br>In this post we will show how to unit test your components in an isolated manner.</p><p>If you want to follow along create a project with <a href="https://github.com/facebookincubator/create-react-app">react-create-app</a>, it comes with Jest. </p><p>To add to your project see <a href="https://facebook.github.io/jest/docs/getting-started.htm">Getting Started</a>.</p><p>The complete example used on this post <a href="https://github.com/vintasoftware/react-jest-blog-post">here</a>.</p><p>Let's start with a simple React component:</p><pre><code class="language-javascript">// components/ArticleTitle.js import React from 'react'; const ArticleTitle = ({ title, url, timestamp }) =&gt; ( &lt;div&gt; &lt;h1&gt;{title} [{timestamp}]&lt;/h1&gt; &lt;h4&gt;&lt;a href={url}&gt;{url}&lt;/a&gt;&lt;/h4&gt; &lt;/div&gt; ); </code></pre><p>To test this component in an isolated manner we can pass the props it receives <code>title, url, timestamp</code>.<br>And check if the output is what we expected. One way we could do this is to check if each element (h1, h4, a) are rendered correctly. Another way to do it is by using snapshot. A snapshot is a representation<br>of the current state of the UI of your component.</p><p>In Jest, snapshots are generated automatically the first time we compare a rendered component to a snapshot. Say we want to create a snapshot of component <code>ArticleTitle</code>. All we have to do is create an instance of <code>ArticleTitle</code> and ask Jest to check if it matches its snapshot. Since <code>ArticleTitle</code> has no snapshot it will create one.</p><p>Let's go ahead and create a test for our <code>ArticleTitle</code> component.</p><pre><code class="language-javascript">// components/ArticleTitle.test.js import renderer from 'react-test-renderer'; import React from 'react'; import ArticleTitle from './ArticleTitle'; test('ArticleTitleTest renders correctly', () =&gt; { const tree = renderer.create( &lt;ArticleTitle title="A title" url="http://example.com" timestamp="2015-12-31" /&gt; ).toJSON(); expect(tree).toMatchSnapshot(); }); </code></pre><p>If you create a project with <a href="https://github.com/facebookincubator/create-react-app">react-create-app</a> you can run the test with:</p><p><code>npm run test</code></p><p>If we run our tests with Jest you will see the following message:<br><code>1 snapshot written in 1 test suite.</code>.</p><pre><code>// components/__snapshots__/ArticleTitle.test.js.snap exports[`test ArticleTitleTest renders correctly 1`] = ` &lt;div&gt; &lt;h1&gt; A title [ 2015-12-31 ] &lt;/h1&gt; &lt;h4&gt; &lt;a href="http://example.com"&gt; http://example.com &lt;/a&gt; &lt;/h4&gt; &lt;/div&gt; `; </code></pre><p>Now, every time the test runs it will compare with the snapshot and when the new snapshot is different, the test will fail.</p><p>NOTE: this file should be kept in your VCS (ie: git).</p><p>Let's see how to deal with components that have other components.</p><pre><code class="language-javascript">// components/Article.js const Article = ({ article }) =&gt; ( &lt;div&gt; &lt;ArticleTitle title={article.title} timestamp={article.timestamp} url={article.url}/&gt; &lt;ArticleBody body={article.body} /&gt; &lt;/div&gt; ); </code></pre><p>We want to be able to isolate the components in a way that the test for <code>Article</code> only tests <code>Article</code>. If we change the code of <code>ArticleTitle</code> we don't want the test for <code>Article</code> to break.</p><p>To do this, we create a mock. First create a folder called <code>__mocks__</code> in the same folder as <code>ArticleTitle.js</code>. Inside the mocks folder create a file with the same name as the one to be mocked <code>ArticleTitle.js</code>.</p><pre><code class="language-javascript">// components/__mocks__/ArticleTitle.js import React from 'react'; const ArticleTitle = props =&gt; ( &lt;span&gt;ArticleTitle {JSON.stringify(props)}&lt;/span&gt; ); </code></pre><p>A trick we use here is to convert the props to JSON. This will only work if your component props are JSON serializable. This is useful because we test that <code>Article</code> is sending the right props to <code>ArticleTitle</code>. What <code>ArticleTitle</code> does with it is not a concern of this test.</p><p>Here is what the test and the snapshot for <code>Article.test.js</code> will look like:</p><pre><code class="language-javascript">// components/Article.test.js import renderer from 'react-test-renderer'; import React from 'react'; import Article from './Article'; // Tell we are mocking ArticleTitle and ArticleBody jest.mock('./ArticleTitle.js'); jest.mock('./ArticleBody.js'); const article = { timestamp: '2016-11-12', body: 'article body', url: 'http://example.com', title: 'An Article', } test('renders correctly', () =&gt; { const tree = renderer.create( &lt;Article article={article} /&gt; ).toJSON(); expect(tree).toMatchSnapshot(); }); </code></pre><pre><code>// components/__snapshots__/ArticleTitle.test.js.snap exports[`test renders correctly 1`] = ` &lt;div&gt; &lt;span&gt; ArticleTitle {\"title\":\"An Article\",\"timestamp\":\"2016-11-12\",\"url\":\"http://example.com\"} &lt;/span&gt; &lt;span&gt; ArticleBody {\"body\":\"article body\"} &lt;/span&gt; &lt;/div&gt; `; </code></pre><p>You can also interact with the component and generate multiple snapshots. Here are a more advanced component and its tests that will generate multiple snapshots. When the mouse goes over the component it adds a class and remove it once the mouse is outside the component.</p><pre><code class="language-javascript">// components/Article.js import React from 'react'; import { bindAll } from 'lodash'; import ArticleTitle from './ArticleTitle'; import ArticleBody from './ArticleBody'; class Article extends React.Component { constructor(props) { super(props); this.state = { isMouseOver: false, } bindAll(this, ['onMouseEnter', 'onMouseLeave']); } onMouseEnter() { this.setState({isMouseOver: true}); } onMouseLeave() { this.setState({isMouseOver: false}); } render () { const { article } = this.props; return ( &lt;div style= className={this.state.isMouseOver ? 'has-mouse-over' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} &gt; &lt;ArticleTitle title={article.title} timestamp={article.timestamp} url={article.url}/&gt; &lt;ArticleBody body={article.body} /&gt; &lt;/div&gt; ); } } </code></pre><pre><code class="language-javascript">// components/Article.test.js import renderer from 'react-test-renderer'; import React from 'react'; import Article from './Article'; jest.mock('./ArticleTitle.js'); jest.mock('./ArticleBody.js'); const article = { timestamp: '2016-11-12', body: 'article body', url: 'http://example.com', title: 'An Article', } test('renders correctly', () =&gt; { const component = renderer.create( &lt;Article article={article} /&gt; ); let tree = component.toJSON(); expect(tree).toMatchSnapshot(); // manually trigger the callback tree.props.onMouseEnter(); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); // manually trigger the callback1 tree.props.onMouseLeave(); // re-rendering tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); </code></pre><h2 id="summing-up">Summing up</h2><ul><li>You can see the full example <a href="https://github.com/vintasoftware/react-jest-blog-post">here</a>.</li><li>Jest is a powerful tool to unit test React components. Run it once and it will make sure that every time you run your tests it creates the same component as the first time.</li><li>Jest uses <a href="https://jasmine.github.io/">Jasmine</a>'s assertions, see the API <a href="https://facebook.github.io/jest/docs/api.html">here</a>.</li><li>You should isolate components. By doing this each test will only cover a specific component.</li><li>Jest snapshots is just one tool to help you write test, it's not a replacement for <a href="http://airbnb.io/enzyme/">enzyme</a>. You will probably still want to use Enzyme.</li><li>Always carefully check the diff when a snapshot test fail before updating the snapshot.</li></ul><p><strong>More from Vinta</strong><br><a href="https://www.vinta.com.br/lessons-learned/"><strong>Check out our Lessons Learned page and subscribe the our newsletter</strong></a><br><a href="https://www.vinta.com.br/blog/2015/javascript-lambda-and-arrow-functions/"><strong>JavaScript's Lambda and Arrow Functions</strong></a></p>