Things to consider when unit testing a frontend

Hello, Habr!



We would like to draw your attention to one more novelty available in our pre-order - a book on unit testing .







The author of today's publication briefly and clearly talks about the advantages of unit testing and TDD using the front-end as an example.



Enjoy reading!



Unit testing is one of the most important approaches that every developer should take. However, I've seen many projects where it can be difficult to put unit testing into practice. There are a number of reasons for this. For example, someone might say that you need to focus on developing features, and writing unit tests in this case is a serious additional burden. Others will note that testing their code is not trivial because the code itself is complex. In both of these cases, the point is missed.

I remember this expression: while wondering if you have time to write unit tests, think about whether you will have extra time to process the same code twice, due to bugs and problems that will emerge in it.

Writing Testable Code



First, let's define what common mistakes make testing your code more difficult. When it comes to front-end testing, one of the most difficult problems I know of is testing user interface components. The main problem that I have encountered in this case is this: it happens that components are too "smart" and are hung with dependencies on other code fragments, for example, from API calls, data loading, event handling, and business logic implementation. Many developers don't like writing tests for such "heavy" components.



The simplest solution to this problem is to separate the components into logic and presentation. With this separation, you can test how the presentation components adhere to the presentation logic, perform event processing and rendering, and focus separately on testing the business logic in the components responsible for the logic.

Ensuring that your components are testable is also a good way to ensure that they are independent, reusable and convenient to use together. This is absolutely necessary, especially when sharing components across projects using popular tools and platforms such as Bit ( Github). Typically, Bit will display and test each of your components separately, and only then send them to a shared collection, thus ensuring that they are indeed reusable - otherwise, what's the point of making them separable.







Example: Exploring Reusable React Components Shared on Bit.dev



A vicious circle: what happens if you don't write unit tests



From my experience with teams that do not apply unit testing properly (that is, do not use test coverage as a metric) either started unit testing too late, or from the very beginning, but did not learn how to practice it. There may be many reasons for this, but I will give a few examples to make it easier for you to decide which one is most similar to your case.



How we test our code during development



In my opinion, unless you stick to test-driven development (TDD), then functionality can be developed provided that the frontend is constantly loaded in the browser. You need to focus on visualizing the functionality and interactions that take place in the user interface, as this is the essence of client-side development.



Only after that, when all the functionality is already working, the emphasis shifts to unit testing.



Here is the main problem that you have to face: writing unit tests is an additional piece of work performed after the development of the functionality is completed. This approach leads to the impression that unit testing is an additional task performed on top of the development of ready-made functionality.



Because of this formulation of the question, both product owners and developers classify unit testing as a cost.



But if you stick to TDD, then everything develops exactly the opposite. Since test cases are written in advance, we don't have to check everything by constantly visualizing the changes, because we have a different way of checking the code during development. In this case, the main verification tool is to write code that will pass the unit test case.



Therefore, I believe that the practice of TDD is the most important stage before the practice of unit tests.



How often unit tests are run



You might be wondering how running a unit test affects how unit tests are written. For example, let's say you have a complete set of tests. You banish it from time to time. Therefore, developers rarely get convinced of its usefulness. In addition, even when the test is found to fail, it is too late.

Therefore, it is important to run unit tests at least at every stage of the pull request build. By adhering to this practice taken from DevOps, we ensure that every new block of code we add passes a set of unit tests. Even if there is a need to change a specific case, the developer will do it before the code merge act.



How often to measure code coverage by tests



As with test execution, this measurement is also important both psychologically and as a practice for developers to judge whether they prepare enough test cases to cover all of the written code.

In this case, I believe that just a dashboard is not enough, in which you can view the coverage of unit tests. But, if it was possible to add an act of measurement when adding new code and thus check the level of test coverage, then such an alternative would be more convenient for getting instant feedback. We could even formulate rules requiring that any new code added to the database should be covered by tests, say, 80%. It is most appropriate to add this validation to the build process of the pull request.



Since developers receive instant feedback on the unit test coverage of their code, they are free to take steps to catch up on such coverage as needed.



Getting out of this circle



If you are already involved in this vicious circle, then the best way to break out of it is to reinforce the practices discussed above. Since the above was about frequent testing to validate newly added code, as well as testing the coverage of new code, you can develop a procedure that is suitable for any operation of adding or changing code.

It is extremely unlikely that you will have enough time to cover all the old code with tests (unless the project itself is completely new) right away, before you have to write new code. Therefore, do not count on this at all, because from a business point of view, it is impractical.
In the meantime, you can make periodic measurements, strategically covering certain parts of the old code with tests. Most importantly, you must train all developers to follow this practice. It is equally important to unlearn how to consider this work as a cost and to convey to all project owners how important it is to write unit tests. Otherwise, many, especially non-techies, may think that unit testing is an extra work, instead of which one could spend time developing new functionality.



So what is the true value of all this work



Unit tests are useful in many ways. If applied correctly, they help to reduce the number of defects in the code, act as insurance when testing existing functionality, do not get damaged when refactoring the code, and also help to keep overall productivity at a high level.

By looking at the code well covered by unit tests, everyone can see how confident it looks.
So let's fill in the gaps and get back on track by adopting DevOps practices and getting used to writing good unit tests while also sticking to test-driven development.



All Articles