Unit Testing ReactJS | Tape vs. Jest

September 05, 2017 0 Comments

Unit Testing ReactJS | Tape vs. Jest

 

 

this post was initially created for tgrecojs.com. I sincerely appreciate all of the feedback and look forward to hearing what you have to say!

Like any developer, my preference of tools that has been built pretty much entirely on past experiences. This means that if i’m not using the tool and it’s known to be an aid to developers then there’s a good chance i’m uninformed on the topic. This was particularly true with Jest, which has become the most widely used library for testing ReactJS applications, so I thought it would be wise to explore the topic a little bit in a blog post.

In this post, I want to share my feedback about recently using Jest while contributing to the next-static project after coming across it on Next.js’ repository.

To make a long story short, I recently ended up pivoting from a dynamic web application to a static site for tgrecojs.com with the help of the next-static project. So far, this solution has worked awesome so I ended up contributing to the project. The first order of business was to write some tests for this project. Testing has a been a focal point in my areas of focus throughout 2017. I received a ton of experience writing unit tests for ReactJS apps and I was ready to jump in.

My first choice for writing unit tests is tape. It’s super lightweight and provides the tools needed to write effective unit tests. A few minutes after getting my environment set up I had the boilerplate for my first test.js file all ready to go.

Below, we can see the code submitted in initial pull request.

// imports….
// set up for test scope
test('<Post />', nest => {
nest.test('given no props', assert => {
const msg = should render a post;
    const props = makeProps();
const re = RegExp(props);
    const el = <Post {...props} />;
    const $ = dom.load(render(el));
const output = $('.post').html();
const actual = re.test(output);
const expected = true;
    assert.same(actual, expected, msg);
assert.end();
});
});

Once ran, this log for this test would look like the image below.

Isn’t that pretty? I’m a sucker for some good TAP which tape produces little did I know that I was taking it for granted but more on that in a moment. At this point, I finally had something that I could confidently push over to the next-static repo.

About a day later, I received this remark from the project owner.

But of course! Why was I using this stone-age technology when I could just plug in Jest! All sarcasm aside I really wasn’t surprised by this. I know how important of a tool Jest has become to so many developers and I wasn’t about to fight that. I was just excited to dive into the framework.

I knew that migrating this super basic test wasn’t going to be hard. I just needed to know how my test was to be set up in order to Jest to work.

As . One of Jest’s marquee behaviors is it’s ability to magically runs tests as long as they are either:

  • in *.test.js or *.spec.js
  • they are in a tests folder.

At the time of writing my test, I wasn’t even aware of this criteria however my code was inside test.js file so everything worked out just fine. Additionally, I could completely eliminate that main index.test.js file as the jest command would navigate to the tests on its own. When jest is run, it will look throughout a project’s tests for specific global variables that it provides users with. In my case, I had to swap out test for Jest's describe function w

Globals In your test files, Jest puts each of these methods and objects into the global environment. You don’t have to require or import anything to use them.

These globals are what allow Jest to magically run our tests without importing any code. I don’t want to be over critical of an extremely well known tool like Jest however I became very conscious of polluting my global scope throughout the last year. (Yes, even in regards to testing). Over time, I became super comfortable with importing tape directly. It only took a write an import statement and I feel comfort in knowing my test code is completely self-contained and thus free from outside bugs. Now, I don’t want to make it seem like i’m nitpicking here. This is merely my feedback on the library. That being said, I think that this no set-up configuration could allows testing to get off the ground as soon as possible.

When my code was modified for Jest’s criteria my unit test looked like the following.

describe('<Post /> with no args', () => {
it('should render a post with default args', () => {
const props = makeProps();
const re = RegExp(props);
const el = <Component {...props} />;
const $ = loadComponent(el);
const output = $('.post').html();
const actual = re.test(output);
const expected = true;
expect(actual).toEqual(expected);
});
});</pre><p id="1b9a" class="graf graf--p graf-after--pre">The test no longer has the <code class="markup--code markup--p-code">assert</code> callback that was used with tape. Instead, we see Jest&#x2019;s <code class="markup--code markup--p-code u-paddingRight0 u-marginRight0"><a href="https://facebook.github.io/jest/docs/en/expect.html" class="markup--anchor markup--p-anchor">expec</a>t</code> function being use to create test assertions.</p><pre id="f664" class="graf graf--pre graf-after--p">expect(actual).toEqual(expected);</pre><p id="55bf" class="graf graf--p graf-after--pre">Specifically, we see <code class="markup--code markup--p-code">expect</code> is making sure the value inside <code class="markup--code markup--p-code">actual</code> is equal to the <code class="markup--code markup--p-code">expected</code> using <code class="markup--code markup--p-code">toEqual</code>. (Learn more about Jest test expectations <a href="https://facebook.github.io/jest/docs/en/expect.html#content" class="markup--anchor markup--p-anchor">here</a>.) Thanks to tape, migrating this test really didn&#x2019;t take much and it felt good know that I could have certainly migrated any test&#x2019;s I&#x2019;ve written to use Jest without any sort of conflict. Same concepts just different libraries. I love sinking my teeth into knew technologies (especially if I can understand them from the jump) so this was a win-win for me.</p><p id="c0b9" class="graf graf--p graf-after--h4">A day after pushing my newly committed test to the PR, I was informed that I failed to fully understand what was being asked of me. I produced a unit test but I really needed to create a snapshot test. Great! Now that I correctly identified the objective, I just needed to find out what exactly a snapshot test was.</p><p id="afa6" class="graf graf--p graf-after--h4">As it&#x2019;s name suggests, a snapshot test will take a <code class="markup--code markup--p-code">snapshot</code> of a component each time a test is run. If there is an existing snapshot, Jest will compare the two to make sure that nothing in our UI has changed unexpectedly. This is meant to provide developers with instant feedback of their UI. Whereas unit test assertions are meant to test a specific behavior, snapshot tests allow us to monitor trivial modifications in our UI.</p><p id="9491" class="graf graf--p graf-after--p">To accomplish this, we use the <code class="markup--code markup--p-code">react-test-renderer</code> library&#x2019;s&#xA0;<code class="markup--code markup--p-code">.toMatchSnapshot()</code> method. Once this is set up, Jest will compare the snapshot of our component to any previous snapshot&#x2019;s and test their contents are the same.</p><pre id="f263" class="graf graf--pre graf-after--p">describe(&apos;Snapshot::&lt;Post /&gt;s&apos;, () =&gt; {<br> it(&apos;should render the contents of the component.&apos;, () =&gt; {<br> const props = makeProps();<br> const el = &lt;Component {...props} /&gt;;</pre><pre id="f7df" class="graf graf--pre graf-after--pre"> const tree = renderer.create(el).toJSON();</pre><pre id="22d1" class="graf graf--pre graf-after--pre"> expect(tree).toMatchSnapshot();<br> });<br>});</pre><p id="9bee" class="graf graf--p graf-after--pre">Below is the exact snapshot Jest created for the <code class="markup--code markup--p-code">&lt;Post/&gt;</code> component. I&#x2019;m not going to explain each line but you should be able to easily reason about what the UI should render.</p><pre id="0e89" class="graf graf--pre graf-after--p">// Jest Snapshot v1, <a href="https://goo.gl/fbAQLP" class="markup--anchor markup--pre-anchor">https://goo.gl/fbAQLP</a></pre><pre id="8314" class="graf graf--pre graf-after--pre">exports[Snapshot::<Post />s should render the contents of the component. 1] =
<article
className="post PostArticle-s1eculme-0 fQItYZ"
itemScope={true}
itemType="http://schema.org/BlogPosting";;
>
<header>
<a
href="/post/test-post"
onClick={[Function]}
>
<h1
className="post--title"
itemProp="headline"
>
Hello
</h1>
</a>
<footer
className="post--info"
>
<span>
<time
dateTime="7/22/2017"
itemProp="datePublished"
>
about 1 month ago
</time>
</span>
<span
itemProp="author"
>
User
</span>
</footer>
</header>
<div
className="post--body"
dangerouslySetInnerHTML={
Object {
"
html": "<p>lorem ipsum is the name making tests is this game</p>
",
}
}
/>
<footer>
<small
className="post--tags"
>
<span>
Filed under:
</span>
<span
className="post--tag"
itemProp="keywords"
>
<a
href="/tag/javascript"
onClick={[Function]}
>
javascript
</a>
,
</span>
<span
className="post--tag"
itemProp="keywords"
>
<a
href="/tag/angular"
onClick={[Function]}
>
angular
</a>

</span>
</small>
</footer>
</article>
`;

As you can see, Jest has created a readable representation of our UI. From this point forward, any future implementations of <Post/> will be tested against this snapshot. To get a better understanding of what this means, let’s see what happens when we remove the <footer> from our Post component and run our snapshot test.

Taking a look at this image, we see that Jest is expecting the <footer> div and it can’t find it inside our component. As a result of this, we see the- sign thus signifying that the code block has been removed from the file. If I had been instructed to remove this footer, I would acknowledge this change and run jest -u to update my snapshot but I wasn’t. Instead, I was creating the initial snapshot for this component and therefore I didn’t have to worry about prior snapshots. The fact that I had created the starting point for futures tests was good enough.

At this point I was further seeing seeing the benefits that Jest bring to a project but I hadn’t been completely sold on it being the end-all be-all of testing frameworks. It’s meant to run super quickly but I found that my tests actually ran a bit quicker with tape which would make sense due to how lightweight tape is.

Additionally, tape allows me to use TAP. Call me old-fashioned but TAP has been around since the 1980’s. That’s older than me and technologies that have lasted this long are usually etched in stone for good reason. I haven’t personally tried to integrate a custom TAP reporter with Jest but from what i’ve gathered it’s fairly difficult task. Is this the end of the world? No. Not at all but it’s worth noting. On the other hand, when the —watch flag is used, Jest offers a pretty cool interface that makes running specific tests a breeze. —watch keeps Jest running after creating snapshot tests and offer us the ability to update our previous snapshots by just pressing u.

There’s no denying that Jest makes it easy to get begin testing code. In fact, easy is putting it lightly. As I just mentioned, it was so easy that I was initially a bit confused as to how this was supposed to work but the point is that it did indeed work. Aside from feeling indifferent about using global variables and not being able to easily print test results in TAP , I have no qualms about the effectiveness of Jest. On the contrary, I am really excited to see what else Jest has to offer as I know that this isn’t the last I will see of it!


Tag cloud