TL;DR Check this awesome library: https://github.com/jakubjanczyk/enzyme-custom-wrappers

Enzyme is a great library for writing tests that I’m using with pleasure. However, in my last project, when our number of tests exceeded 1000, we started to encounter issues with maintainability and keeping them clean. Another thing is that we aimed to have as many integration tests as possible, to have more confidence that they are actually checking if everything works as a whole, not just as separate parts. To achieve that, we exclusively use mount function from Enzyme. It doesn’t mean that all tests render the whole application — we test single components as well, but we never mock any sub-components. Moreover, we had a lot of issues using shallow rendering from Enzyme, so decided not to use it anymore. One example would be that shallow makes it harder to refactor the code. In any case, we wanted to extract a separate component — we had to change our shallow tests, as the structure changed. The content of this new component was no longer rendered in our test.

As an example to illustrate the issues we encountered, let’s consider a date picker component used in multiple places of the application. Let’s say that when we change a date, something on the page changes (chart or table content, for example). Now, when testing one component that uses our date picker we want to trigger a change (and then we assert some change in the other component). Here’s how we could do it:

Looks okay, but then we have another part of the application where we do this:

So we have the same code duplicated twice. Then every time we want to use this component, and have it properly tested, we have to duplicate it again. You can imagine we could end up with a significant number of duplications.

Now imagine a situation that at some point you change the way of finding a date picker component (for example, by changing a class name) or even worse, you change the external date picker component and the logic for changing a date is now different. That’s a nightmare — you have to go to all places and change it there! Well, maybe 3 tests aren’t that bad, but imagine it with 100.

You could now say — there is a simple solution, just extract a function and use it in those places. That’s correct… but at some point, you could have thousands of such functions and you have to find which one you want to use and to not duplicate names. The nightmare returns!

What if I could tell you that it doesn’t have to be hard? That you could easily create your own, dynamic DSL for any component? That you could write those tests above like this:

There’s nothing simpler; just keep reading to find out how!

Introducing enzyme-custom-wrappers

We aimed to make our tests more readable, maintainable and resistant to changes (as much as possible), while letting us keep our way of writing mostly integration tests and still using Enzyme for that. The last one was important, as we had tons of tests written with this library and didn’t want to change them all.

After some experimentation, we ended up with idea to extend ReactWrapper, that is used by Enzyme, with our own, custom methods available directly on the object that is being returned by mount function. As it turned out, it was also possible to add some new common methods that are not available in Enzyme, but are used all over our tests. And we are still keeping current Enzyme methods available!

Let’s take a look at how it works in action first:

Now, mountWithCustomWrappers function is our custom function that wraps Enzyme’s mount. I will get back to how it works in one of the subsequent sections.

The wrapperForShoppingList and wrapperForShoppingCart are our custom wrappers that define our DSL. You can spot that later we use addItem and itemsInCartCount methods on our mounted component. They come from those wrappers. Now let’s see how they look:

At first, you can see that there is no magic here: the wrapper is just a function that receives the mounted component and returns an object with a set of functions. These functions would then become available on our component object in tests. You can see that we can merge multiple wrappers (2 in this case, but it can be as many as you need).

Common built-in functions

The component object that comes as an argument to the wrapper function is not a simple Enzyme’s wrapper. We discovered that we were using a lot of similar operations in our tests and decided to extract so-called (by us) “CommonWrapper” that extends the basic wrapper with some common operations. At the time of writing, we have defined: findByClass , findByDataTest , findByText , click , focus , typeText or blur . I think the names are self-explanatory, however I will show you how some of them are implemented later.

One note about findByDataTest function. We decided to introduce the custom attribute data-test on HTML/React elements, so we can easily find the ones we need in tests. We didn’t want to use others, like className or id , as they are less stable. We wanted to avoid rewriting tests if the class changes. Therefore, this function exists. There are different conventions; we decided to stick with data-test , but I also saw the usage of of data-test-id or others. It’s up to you. If you don’t want to pollute your production code with it, just use this babel plugin to remove it: https://www.npmjs.com/package/babel-plugin-remove-jsx-attributes

Going back to our wrappers. Let’s take a closer look at addItem function:

I intentionally made it a little more complicated than it needs to be, to highlight some of the most important features of this solution. At first, you can see that I’m using standard find function from Enzyme. Then I can use my common function findByDataTest or findByClass to search for subsequent elements. Finally I am using click function to click on a button that I just found in my DOM.

For the basic cases, that’s really it — no magic, just a few functions and objects.

You might not need wrappers!

Of course, you might not need to add any custom wrapper and still be able to use some of the common functions, especially if the test is really simple:

No custom wrapper, just basic functions used :)

But I heard you like wrappers, so I put a wrapper inside your wrapper

Now, let’s go one step further and leverage one more big advantage from the whole presented mechanism — you can easily nest your custom wrappers inside other wrappers! Let’s look at the following example:

As you can see, I set up our component with only one wrapper, but I am using similar methods as before. The wrapper itself is unchanged, but it uses two other wrappers shoppingListWrapper and shoppingCartWrapper that we used already. Here, I am using the new function createComponentWrapperFor to initialize those wrappers (to add methods that I need). In specific functions, like addShoppingItem I am using this wrapper to wrap the provided component and use addItem function that we already discovered as well :) createComponentWrapperFor function is actually used inside implementation of mountWithCustomWrappers .

Namespaces — FTW!

One issue we had at some point is that when you combine multiple wrappers in a test, you could run into names duplication, and by having two methods with the same name, the latter one would overwrite the former. There is a simple solution to this as well. And you don’t need anything except simple JS knowledge — just use namespaces, or modules if you want, as there is no official name for it :)

Let’s consider that on our Shopping Page, we have two “Buy” buttons — one on the shopping cart and one at the top of our items list — and in our test, we want to click the former one. We could just write:

But what if we want to have buyButton function on both wrappers? We cannot do this. One solution is to change their names, for example to cartBuyButton , but you can imagine it might get cumbersome at some point. The ideal solution might be to create namespace on wrapper, simply like this:

Nice and easy — no names clashing and we keep our test code very readable.

Why would you use it

We’ve used this approach in 3 projects until now and have written more than 2000 tests with one conclusion — it is working (at least for us; of course it doesn’t have to for you). We made a lot of adjustments in the process and it’s still in progress. But it gives us a few very important advantages:

You can write cleaner tests, with use of specific DSL for your needs, while leveraging the power of regular Enzyme. It means there are no implementation details obscuring the test. The test looks like a list of actions performed by the user of the application. A lot of code can be reused across the tests. It’s very easy to adjust tests to changes. If you click on a specific button in 100 tests and at some point you change a way to access this button — just do it in one place, in wrapper implementation. In the case of my project — we had a few lists hidden inside expandable sections. At one point, we decided that we don’t want to render anything in the section that is not open. This meant that if we want to click a button inside a section that is closed in the test, we need to open it first. And we had hundreds of such tests. Thanks to the aforementioned approach — we just changed it in one place and it was working like a charm! In theory it would be quite easy to change the testing library, or even the whole framework. You have a set of tests and those tests only describe domain cases, without many implementation details. So just change wrappers implementation and voila! However, I haven’t tried it yet, so don’t quote me :)

How to use it

You can just install it from NPM:

npm install --save-dev enzyme-custom-wrappers

And it’s written in TypeScript, so if you are using this language for your React projects, you can use this library as well, without any issues. Please look at the documentation in repository to find out more about usage with TS.

If you are not using Enzyme, ensure to try this awesome library created by Kent C. Dodds: https://github.com/kentcdodds/react-testing-library.

We’ve actually ran into it about two weeks after we created the first version of our custom wrappers, and got inspired in a few places. Nevertheless, we still needed something for Enzyme, so that’s why we didn’t just use this library, but created one ourselves.

Conclusion

I hope you like it and would give it a try in your projects. There are many features that can be added (or more common methods), as I’m well aware, but I think it’s a good start. I would love to hear any ideas, opinions or problems you have regarding the library. Just write a comment here, add new issues on GitHub or contact me directly: jakub.janczyk@pragmatists.pl / @janczyk_jakub on twitter

Until next time and have fun! :)