Skip to content

Why automate testing?

There are many reasons why automated tests are useful but my favorite reason is: you’re already testing.

For example, you’re adding a new button to a page. Then you open this page in a browser and click this button to check whether it works — this is a manual test. By automating this process you can be sure that features that used to work will always work as they should.

Automated tests are especially useful for rarely used features: we always test whether the button submits the form with all fields filled correctly, but we tend to forget to test that checkbox hidden in a modal and only used by the boss of your boss. Automated tests will make sure it still works.

Other reasons to automate tests are:

Confidence to change code: well-written tests allow you to refactor code with confidence that you’re not breaking anything, and without wasting time updating the tests.

Documentation: tests explain how code works and what’s the expected behavior. Tests, in comparison to any written documentation, are always up to date.

Bugs and regression prevention: by adding test cases for every bug, found in your app, you can be sure that these bugs will never come back. Writing tests will improve your understanding of the code and the requirements, you’ll critically look at your code and find issues that you’d miss otherwise.

Automated tests make it possible to catch bugs before you commit them to the repository, in comparison to manual testing where you find most of the bugs during testing or even in production.

What to test?

The testing trophy, introduced by Kent C. Dodds is popular for the frontend tests:



Roadmap

It says that integration tests give you the biggest return on investment, so you should write more integration tests than any other kinds of tests.

End-to-end tests in the trophy mostly correspond to UI tests in the pyramid. Integration tests verify big features or even whole pages but without any backend, a real database or a real browser. For example, render a login page, type a username and a password, click the “Log in” button and verify that the correct network request was sent, but without actually making any network requests — we’ll learn how to do it later.

Even if integration tests are more expensive to write, they have several benefits over unit tests:

Unit tests Integration tests
One test covers only one module One test covers a whole feature or a page
Often require rewrite after refactoring Survive refactoring most of the time
Hard to avoid testing implementation details Better resemble how users are using your app


The last point is important: integration tests give us the most confidence that our app works as expected. But it doesn’t mean, that we should only write integration tests. Other tests have their place but we should focus our efforts on tests, that are the most useful.

Now, let’s look closely at each testing trophy level, from the very bottom:

1- Static analysis catches syntax errors, bad practices and incorrect use of APIs:  — Code formatters, like Prettier;  — Linters, like ESLint;  — Type checkers, like TypeScript and Flow.

2- Unit tests verify that tricky algorithms work correctly. Tools: Jest.

3- Integration tests give you confidence that all features of your app work as expected. Tools: Jest and Enzyme or react-testing-library.

4- End-to-end tests make sure that your app work as a whole: the frontend and the backend and the database and everything else. Tools: Cypress.

Unit testing best practices

Let's look at some best practices for building, running, and maintaining unit tests, to achieve the best results.

Unit Tests Should Be Trustworthy

The test must fail if the code is broken and only if the code is broken. If it doesn't, we cannot trust what the test results are telling us.

Unit Tests Should Be Maintainable and Readable

When production code changes, tests often need to be updated, and possibly debugged as well. So it must be easy to read and understand the test, not only for whoever wrote it, but for other developers as well. Always organize and name your tests for clarity and readability.

Unit Tests Should Verify a Single-Use Case

Good tests validate one thing and one thing only, which means that typically, they validate a single use-case. Tests that follow this best practice are simpler and more understandable, and that is good for maintainability and debugging. Tests that validate more than one thing can easily become complex and time-consuming to maintain. Don't let this happen.

Unit Tests Should Be Isolated

Tests should be runnable on any machine, in any order, without affecting each other. If possible, tests should have no dependencies on environmental factors or global/external state. Tests that have these dependencies are harder to run and usually unstable, making them harder to debug and fix, and end up costing more time than they save.

Unit Tests Should Be Automated

Make sure tests are being run in an automated process. This can be daily, or every hour, or in a Continuous Integration or Delivery process. The reports need to be accessible to and reviewed by everyone on the team. As a team, talk about which metrics you care about: code coverage, modified code coverage, number of tests being run, performance, etc.