Read article6 min
Written by Jan Hoogeveen
How we test client web projects with minimum effort for high value
A developer coding with dynamic text effects.
i

We’ve recently run into the issue of needing to refactor a large React app for a client. The app contains around 50 possible views with numerous variations because of a set of filters and user settings. The components we needed to refactor touch upon nearly all of those variations, so those refactored changes spread out across a huge surface area of the app.

After realizing we needed to check all these variations by hand — and a nerve wrecking push to production — we decided to re-evaluate our current testing practices, and introduce new testing practices to to our process.

A Problem of Balance

When working at an agency, it can be really hard to maintain balance; on one hand, you want to deliver features, and on the other, quality and adhering to best practices.

In an ideal world, you want to push new features to production every sprint, if not more often. However, the more complexity involved, the higher the risk of new features breaking other parts of your application.

You could hire a dedicated person to set up a quality checklist, maintain that list, and check off its boxes after every new release. While there is some merit to manual testing, it’s hardly a scaleable solution, and at best should be used in addition to automated tests.

This post describes how we, at CLEVER°FRANKE, approach testing while building interactive, web applications for our clients.

Context: most of our client projects are in active development at our studio for three to five months before we finish it and start a new project.

Automated Testing

I’m not going to write about the benefits of writing tests for your application. There are enough resources on the internet that explain the why. What’s more interesting is the practical approach of actually implementing automatic testing in real-life projects.

Unit testing React components

You could argue that a React component is a unit, and I’d agree to some extent, seeing as props go in and a representation of the updates DOM comes out. In that sense, let’s discuss how we unit test our React components.

We tried to do this vigorously at first using a combination of shallow rendering tests and full-rendering tests through Enzyme. We found while it’s not impossible to write valuable tests with Enzyme, our experience writing tests was just not so great. Instead of quickly writing a test and going on with our jobs, it felt like an eternal fight Jest and Enzyme all the time.

We made the decision to stop writing unit tests for React components. Not because we don’t value good tests, but because the effort of writing those tests was working against us.

Instead, we focused on writing unit tests for smaller, helper functions that are used throughout the application.

A good example of this is our custom number formatting helper used throughout the application. This is an isolated piece of functionality where we can assert a given input (a number) always returns an expected output (a string).

Unit tests for a custom helper function which takes a number as input and return a human readable string for the User Interface

Snapshot testing

We use the snapshot testing feature, introduced by Jest in 2016, quite extensively, especially for dumb components, where we find it a match made in heaven.

When testing (UI) React components, what we care about is that when we feed-in specific props, our components return the expected DOM representations. Whenever the output of a component changes unexpectedly, that’s an indication of a bug, or we need to update our snapshots.

At first, we considered snapshot tests a ‘good enough’ replacement for writing tedious unit tests with Enzyme. However, several months later, we came to the conclusion that snapshot tests are in no way a suitable replacement for actual unit testing at all.

One particular case in which we found this valuable, and the only reason we still use snapshot tests today, is how it shows the direct impact of changes we introduce with styled-components. You can immediately see if changing the style of a component has unintended side-effects (or expected side-effects) on other components.

This is a powerful tool when composing styled components and using theme providers.

JavaScript test file
Snapshot test of a custom React component extending React-Routers Link component with new functionality.

Discovering react-testing-library

A few months ago we discovered a library called react-testing-library. Now, after trying it out in some projects, we came back on our decision to stop writing unit tests. We like it for a few reasons, but one of the biggest ones is convenience. Compared to Enzyme the API surface is quite small, and you only need to learn two or three methods to actually write valuable tests.

The library is built on the idea that a developer should test components in roughly the same way a real user would, without having to worry about implementation details. This ensures that you don’t need to rewrite your tests as soon as implementation details of React change. Better still, the API of react-testing-library makes writing these kind of unit tests relatively straightforward!

Getting started writing unit tests with this library is relatively easy and straightforward. It feels like a fresh start in our codebase and we recommend trying it out in your next projects!

End-to-End testing

Just testing the units or components alone does not cover everything. An application consists of many components working together. This is where naive state management or side-effects in your application can result in nasty bugs. Of course, this is exactly the issue we faced after a huge, refactoring sprint. We were certain the components in isolation were still working as expected thanks to the unit and snapshot tests. We were however, unsure if the application as a whole still worked as expected. This is where end-to-end testing shines.

We tried solving this in an automated fashion before. We even added one E2E solution to our now deprecated, open-sourced starter package for Universal React Applications. Unfortunately, we found none of those solutions really helpful in the end. They were either based on outdated browsers, lacking documentation, or simply too hard to configure and use. We’re looking for a solution that requires minimal effort from our developers to create really solid tests.

Thus, we reverted back to manual testing every single time. We’d prepare a new release of a product, deploy that to a QA environment, and invite people internally to break it. While we did squash a lot of bugs, this is an error-prone, repetitive, and not a scaleable solution.

After working on the recent refactoring round, we decided we needed a solution to solve this once and for all. What we want is an easy to use, documented, stable solution to test our complete application.

Eventually, we found Cypress.

Painless end-to-end testing

Cypress is a next generation front end testing tool built for the modern web. We address the key pain points developers and QA engineers face when testing modern applications.

Things that used to be hard using other solutions, are now really easy using Cypress. Stubbing external resources, waiting for data to load, providing sane timeouts for tests to fail, debugging, Cypress takes care of almost everything.

Here’s a screencast of cypress running basic tests in a GUI on the District Department of Transportation website, one of our client projects.

The GUI is an amazing tool. It shows you an overview of available test scripts, it allows you to pause and resume testing, inspect the application you’re testing with a web inspector, and it even features time travel. Want to go back in time to see how your DOM looked in the middle of your test just before throwing an error? No problem!

Of course, a GUI is nice but E2E testing needs to happen on your continuous integration service as well. Cypress comes with a CLI mode, suitable for running tests in your CI/CD environment. Implementing it is relatively easy thanks to the configuration examples.

We are always looking for the easiest solutions to test our web applications. In my opinion, using both react-testing-library and Cypress is a no-brainer for our current projects. Using these two tools, tests are easy to write, provide a lot of value and are easy to hook-up to your CI/CD provider.

Now, our team can rest assured knowing that their commits don’t break existing functionality and deploy with confidence.

If you’re curious about react-testing-library and Cypress, take a look at their sites.

Read, see, play next
Smart city sensor visualization
see

Smart city sensor visualization

Real-time visualization of the Smart City sensors

Foursquare visualization on city activities
play

Foursquare visualization on city activities

Virtual activity of cities

Dynamic color research visualizations
play

Dynamic color research visualizations

Visualizing the color data of our projects with motion