Unit Testing ReactJS | Tape vs. Jest

Thomas Greco
CloudBoost
Published in
8 min readSep 5, 2017

--

Introduction

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 needed to pivot from a dynamic web application to a static site 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.

Step One — Produce an undesirable test and push it to the repository.

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.

Becoming one with Jest

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', () => {

});`

The test no longer has the assert callback that was used with tape. Instead, we see Jest’s expect function being use to create test assertions.

expect(actual).toEqual(expected);

Specifically, we see expect is making sure the value inside actual is equal to the expected using toEqual. (Learn more about Jest test expectations here.) Thanks to tape, migrating this test really didn’t take much and it felt good know that I could have certainly migrated any test’s I’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.

You had one job

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.

Enter Snapshot Testing

As it’s name suggests, a snapshot test will take a snapshot 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.

To accomplish this, we use the react-test-renderer library’s .toMatchSnapshot() method. Once this is set up, Jest will compare the snapshot of our component to any previous snapshot’s and test their contents are the same.

describe('Snapshot::<Post />s', () => {
it('should render the contents of the component.', () => {
const props = makeProps();
const el = <Component {...props} />;
const tree = renderer.create(el).toJSON(); expect(tree).toMatchSnapshot();
});
});

Below is the exact snapshot Jest created for the <Post/> component. I’m not going to explain each line but you should be able to easily reason about what the UI should render.

// Jest Snapshot v1, https://goo.gl/fbAQLPexports[`Snapshot::<Post />s should render the contents of the component. 1`] = `
<article
className="post Post__Article-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.

Final Words

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!

--

--

Work in JavaScript development. Believe myself to have a keen eye for design, but we'll see #bwa