A crumbling tower stands on the Cornish coastline

Cleaner React Enzyme Tests

Greg Malcolm
Beam Benefits

--

Ever wish your Jest/Enzyme tests were a little more robust? Here are a few things that have worked well for us at Beam.

Let’s take a look at a couple of tests in a fictional toothpaste tasting app:

A good test suite will usually contain many more tests, but I’m intentionally keeping this brief. There are a number of things I could do better:

  • I’m generating a fresh component wrapper for every test from scratch.
  • Each it block is convoluted with multiple expect assertions.
  • Those expect assertions are verbose and make for hard reading.

Reusable Wrappers

Rather than rebuilding the same component through mount for every single test, how about we create a helper method that generates test components?

For our original sample code, this would be enough to cover it:

Admittedly, this is only a minor improvement. This approach tends to shine when you have the need to overwrite property defaults. For example, consider this wrapper builder for a Monty Python inspired component:

In this particular case, there are number of possible fields. As can be seen at the bottom of the sample by calling just buildWrapper() we can choose to take the default form field props (“Lancelot,” “To seek the Holy Grail,” and “Blue”).

When we invoke it with some overrides for name and answer in the second example we get “Robin,” “To seek the Holy Grail,” and “Blue”.

Also a bonus, we’re overriding the onChange event so we can confirm that it gets invoked when it becomes relevant to our tests.

Break Up the Tests

A gathering of Meeseeks cosplayers

Currently each it block is rather busy. It can be argued that a good test should test one thing and one thing alone. These it blocks are testing multiple things.

We can clean this up by replacing each of the it blocks with nested describe groups. We can then have a separate it block for every expect statement. To avoid repetition and to keep those it blocks super lean, we can move preparation and cleanup into beforeEach and afterEach.

Let’s give it a spin:

Two things have changed:

  1. The component wrapper is now created in a beforeEach function which runs before each of the tests in the new describe group.
  2. The two expect statements no longer live in the same tests. Breakages will now be reported independently in the test report.

There’s still room for improvement. That 2nd it block is still looking bulky. We can fix that by breaking it out into a further describe and splitting the work between a beforeEach and an it.

Almost there! Notice how we have to call unmount every time? This is because we’re using a shared wrapper variable accessed by all the tests in the suite. We need to unmount to make sure we’re not getting contamination between tests. We can actually move that unmount into the root describe and reuse it for every test in the file.

There is one caveat; we should check if the wrapper was used beforehand before calling unmount. Here’s how that we can do that:

More Readable Assertions

Finally, let’s make our assertions easier to read.

Take a look at this one:

What’s wrong here? Verbosity.

The brain is really good at scanning short statements at a glance. For example:

“Disabled button!”

The more words you add, the harder it is to do that. This takes a moment to read:

“The test button at the bottom of the page should be disabled but it is not”

The same principal applies when reading code. If we want to make our assertions easier to read, we have to keep them brief. I tend to go about this in two ways:

  1. Wrapper selections such as wrapper.find(“button”) tend to be highly reusable, so I like to place them at the top of the file in the form of helper methods. For example, I might create one for the taste button called tasteTestButton(). Making them methods works well because every reference is pulling from the latest component state.
  2. If the expect is still looking long, I also extract it out into a local variable just before calling expect. For example, an expression like tasteTestButton().prop(‘disabled’) could be extracted into a variable named disabled.

Revisiting our Earlier Code

We started out with a sample of two tests. If we follow all the principles discussed so far, we end up with this:

Summary

This is what we‘ve done:

  • Avoid repetitive wrapper creation code by creating a builder function for constructing wrapper components.
  • Break up those big it blocks using inner describe groupings.
  • Keep those expect statements nice and brief.

But most importantly, refactoring does not end at the source code. Tests need love too.

--

--