As a frontend developer I do TDD by default

Alex
Mercadona Tech
Published in
5 min readDec 1, 2021

--

I am Alex Torres, frontend developer at Mercadona Tech since 2018. In this post, I want to tell you about the different phases we have gone through in terms of how to test our frontend applications, how we have gone from having more than 2000 tests to having as of today 700 (data based on the store app).

“As a frontend developer I do TDD by default” is a phrase written in the form of a user story because we want to emphasize that we are a team of product engineers where what matters is the value we provide. But how do we ensure that this value is of sufficient quality?

Quality?

In our development flow, a user story goes through different phases in which we make sure that we are going to add value. We all consider the best solution from product analysis and understanding the problem to design validation through research or engineering, thinking about the best way to implement the one that enters.

Technical excellence is not negotiable.

Delving into that best solution by engineering, we understand quality as a set of actions that we take when we start working on that solution, from doing pair programming, dividing the user stories, doing TDD, applying outside-in strategies, or even maintaining simplicity. In our code, as we explained in the post of Engineering at Mercadona Tech.

Where we come from?

Back in October 2018, when I joined Mercadona Tech, one of the things that I valued the most was that TDD was not negotiable, it was part of our development flow, and the whole team was aligned in that this was our way of working.

Regarding our stack, we used React, and at that time, Enzyme was the most used together with Jest, and it seemed that it worked very well.

Our way of doing tests followed the famous pyramid of tests in which unit tests predominate, following the integration tests and above the end to end (E2E), which are the most expensive.

Testing pyramid

This led us to the fact that when you opened any folder in the project in all our applications, they all had an associated test, from elementary components such as a button to domain functions, high-order features, or even Redux actions. Example of what a test from long ago would look like:

Unit test

We were not entirely comfortable with this type of testing when we made a refactoring; we had to change the tests, and this already seemed like a code smell. On the other hand, we were confident that this component, as a unit, was going to behave as our tests said, but we were not sure that the interaction between them would work; this is another smell.

We were learning and changing our way of doing tests. We started creating tests from higher points of the application; we changed the focus to tests that looked more like user actions (I see a button, I click it, and things happen) and not so much coupled to the implementation. All of this seemed like it was the right direction to go, but this had a big problem: dependencies.

The higher we ran the tests, the more dependencies appeared (Redux, react-router, props, fetch …). It made the arrange step of our tests harder to write and maintain.

A key point in our new way of testing was encapsulating all these dependencies in a library, which started as a simple function and continued with middleware.

Another critical point is the change from Ezyme to Testing library since with the latter, we were closer to our new way of writing tests, fewer units more user interactions, and it also helped us delve deeper into accessibility issues. Our colleague Juan Diego wrote about this in his post Web accessibility at Mercadona Tech.

Where are we now?

Today we do tests by mounting the entire application at its highest point and writing tests in the form of user actions, using our library to improve the readability of the tests and get rid of a lot of boilerplate. This is an example of what a test looks like today.

Integration test

As we can see, we mount the App at the home route (“/”) with its respective backend calls mocked, login and all that it implies (calls to the backend, user saved in storage, valid UUID …). Then we wait to see the title “Next delivery” so that the application is mounted asynchronously, and we see that the “What’s New” alert is on the screen. We close the modal with the function (here, we use helpers functions that help us encapsulate the user’s actions). Finally, we hope that the “What’s New” modal is not on the screen.

As you can see, this would be a behavior that our users would do given a use case. It helps us so that if we have to refactor, we don’t have to update our tests since the business rule remains the same. Another point to take into account is the way we access DOM elements, using the getByRole function. It makes us build more accessible components, as I have commented before.

Considering the previous test, we could go from implementing a local React state (left screenshot) to change it with Redux (right screenshot). Our test would remain green since the business rule would be the same for both cases, and the only thing that changes is the implementation.

Refactoring

On a final note

We’ve been continuously learning and improving our way of ensuring code quality, but this approach works for us. Today we believe that we add value with the appropriate quality, but we continue to improve day by day to be better than yesterday.

--

--