Front-end Testing: Static vs Unit vs Integration vs E2E
Front-end testing is a big part of development. The benefits of adding tests to our apps are clear, but it can be confusing. There are a lot of test types, and their definitions may vary depending on who you ask, the tools you use, and the ways we can test code. The internet provides a few examples of "roadmaps” for testing front-end, like the testing pyramid or the testing dorito. But the Testing Trophy by Kent C. Dodds is the one that caught my eye. Let’s go through each segment of this testing trophy and see its pros and cons.
What is static testing?
Static tests should be the base of front-end testing. They consist of linters (e.g. ESlint) and static type checking (e.g. Typescript).
Static testing with ESlint
Installing the required packages takes only a few minutes. It's a good idea to tweak your IDE to run ESlint on every save or to use some tools like Husky and Lint-Staged to run it on every commit. This way, you make sure that every line of code that goes into a remote repository is checked and hopefully fixed by ESlint.
Static testing with Typescript
In this example, we are saying that the parameters "a" and "b" should be numbers and also that our function will return a number. So whenever you try to pass a string as a function argument, Typescript will catch it and throw an error. Using Typescript is the most challenging part of static testing as it requires developers to learn some new syntax.
Front-end unit testing
Unit tests are pretty simple because their focus is to test separated parts of the app. No external dependencies, no software frameworks. It can even test a single function; for example, we can add a unit test for the function "sum" seen in the previous example:
Unit tests are pretty simple and can be read as standard English: "Expect that sum with arguments 1 and 3 will result in 4." According to the Testing Trophy, they are a good starting point for more complex tests. If you are interested in this kind of test, take a look at the Jest library. It doesn't require a lot of configs and generally works great, provided you have enough helpers to test everything you need. You can also create more complex test cases with mocking API requests and responses.
As part of your product development, you should focus on testing the crucial parts of the application as well as its logic. Otherwise, you might find yourself testing implementation details, and that's unnecessary. So far, your code is covered by static tests, and some parts of your app have their own unit tests. Let’s see the next type of tests, that is, the integration.
Front-end testing: the integration tests
Integration tests ensure that various parts of the app work together. They’re crucial from a business viewpoint. Remember that users don't care if a single function of your app works. They are interested in knowing if they can use the whole application, and that's where integrations tests shine.
The React Testing Library is a go-to resource for integration tests. Its main goal is to allow for testing the way your users will use the app and avoid testing implementation details. RTL offers a ton of utils to make testing more straightforward and more maintainable. Let's look at the following snippet:
This simple component consists of a label connected with its input, a button to show the message, and a paragraph in which we will display it.
Now let's add a test for it:
Let's go through this example:
1. We import a few helpers provided by RTL alongside the component to be tested.
2. We create a new test and name it.
3. We use the render method from RTL to render the component. This method provides so-called queries that will let us select the elements needed in the test.
4. We use the provided queries to select the input, the button, and the paragraph where the message is displayed. We can choose the elements in different ways even when they show up asynchronously, and we can use regexp (like here, case-insensitive). The most interesting is "getByLabelText" because we can select the input by first choosing the label connected to it. We can also check if our inputs are accessible.
5. There shouldn't be any message in it, so we test for that.
6. We are using a helper to simulate the user’s input.
7. We simulate another event: click on the button.
8. We add another expectation to see if the message container contains the provided value.
Hopefully, our test passes!
Front-end testing: End-to-End tests
The last part of the Testing Trophy is End-to-End tests. E2E tests are different from other types of tests because they run in a real browser. We write test cases as step-by-step instructions for the automated browser to go through the parts of the app we want to test.
One of the most popular tools for End-to-End tests is Cypress. It's easy to set up and to use, on top of being fast. Let's see how the test for a registration form looks like:
1. We create our test
2. We tell Cypress to visit the URL with the registration form.
3. We take proper inputs and type some values.
4. We look for a button with a "Register" text, and we click on it
5. After successfully registering, we should be redirected to the "/login" page, so we test for that.
When we run this test, a browser window pops up, so we can see the test runner perform each step. If something goes wrong, Cypress lets us know.
Usually, End-to-End tests take longer to write than the unit or integration tests. They also take longer to run as they communicate with a real API, so you should only write them for the most critical flows in the application.
Last words on front-end testing
The Testing Trophy is a great guideline. We can start with static tests, then move on to unit tests, and finish with integration tests. We can leverage the power of end-to-end tests to automate the testing of the app’s most critical routes. Drop us a line to learn more about front-end testing or write to firstname.lastname@example.org
You may also like...
Congratulations, you’ve just come up with an awesome startup idea that’s going to change the world! And afte...