Designing Effective Application Tests
Discover the art of crafting high-quality application tests in this comprehensive guide. Learn strategies and use tools to create robust, efficient, and maintainable tests.
Info: I’m currently in the phase of heavy editing and finalizing the book. Therefore, I decided to don’t use chapter numbers anymore because the numbers used for the newsletter do not fit with the latest structure of the book. Furthermore, you might notice some of the content in this chapter I already wrote about in earlier issues of this newsletter. This is also because of some of my latest edits to the book's structure.
Disclaimer: You are reading an early version of the text! The final version of the book will be revised and may contain additional content.
Creating robust, efficient, and maintainable tests is a challenge every developer faces in the ever-evolving software development world. When done right, Application Testing can serve as a safety net, catching missteps before they become costly errors. But that's only one aspect of it. This chapter will equip you with the strategies and tools to craft high-quality tests that not only ensure your application's functionality but also serve as valuable documentation.
We'll discuss the fundamentals of Application Testing, for example, why it is important to test every new feature and where we draw the line between what qualifies to be part of our application and what is not. We'll then delve into Domain-specific Languages (DSL), exploring how a custom DSL can make our tests more resilient and readable. By the end of this chapter, you'll have a solid understanding of how to design effective Application Tests that are robust, efficient, and aligned with the requirements of your business domain.
The Tools of the Trade
Before we delve deeper into the intricacies of Application Testing, let's discuss the tools we'll use. As of writing this, I recommend using Playwright and Vitest as the testing frameworks of choice. But as mentioned earlier, keeping ourselves independent from particular tools is vital. Tools come and go, but high-quality software and its accommodating tests stick around for years and sometimes even decades.
That's why, as we advance, apart from some anti-pattern examples, we write all of the tests in this chapter using the generic driver abstraction we set up earlier in the chapter about the perfect test setup. Utilizing the driver abstraction allows us to decide which particular test framework we use when running our tests or swapping our frameworks whenever needed.
Application Testing Fundamentals
When we want to take testing seriously, it is crucial to write Applications Tests for every new feature before we start implementing it. We write our first Application Test shortly before we start writing the actual code.
This proactive approach ensures that we build a safety net that guarantees everything works as expected before we deploy the new feature to production. Furthermore, those tests will catch any future missteps when working on new functionalities, providing confidence that the new feature won't inadvertently disrupt existing functionality. But the most crucial aspect of why we want to have tests in place before starting to work on a new feature is the rapid feedback loops they enable. After writing a couple of lines of code, our tests will tell us whether we made a mistake or everything works as expected. And as soon as we satisfy all Application Tests, we know we're safe to deploy.
What Is an Application?
As the name suggests, we want to test our whole application with Application Tests. But what exactly do we mean by application? I define an application as everything within the boundaries of a VCS (e.g., Git) repository. We must ensure that our Application Tests focus only on the behavior of our application, disregarding the implementation details of external systems (everything outside of our repository). This approach allows us to concentrate on the factors we can control over external factors we can't.
Or, to look at it from the other side: if your tests rely on behavior from external REST or GraphQL services, why are the tests located inside your application's repository? It would make as much sense to put them inside the repository of the REST service, for example. And to take it one step further: if a test breaks because of an error in the REST service, who is responsible for fixing it? Your repository is the single piece of the system you're responsible for. It is on you to guarantee that your software works as expected as long as it adheres to specific contracts with other system parts. You must ensure you stick to those contracts, not more (but also not less)!
We must cover every critical feature within the boundary of our repository with Application Tests to fulfill our part. Therefore, it is an anti-pattern to mock any code within that boundary. Mocking, in this context, refers to creating a fake version of a component, module, or HTTP service for testing purposes. While mocking external services is acceptable and even mandatory, mocking anything within the repository can lead to false positives, tightly couple our tests to the implementation, and might mask underlying issues. Therefore, we treat everything within the repository, our application, as a cohesive unit we test in its entirety.
Decoupling from Implementation Details
The previous chapter taught us the three principles for writing good tests. As we learned: decoupling from implementation details is one of them. Decoupling from implementation details is essential no matter which kind of tests we're talking about. But with Application Tests, it's mission-critical!
Our Application Tests serve a vital role not only during active development but also as documentation when we or some other stakeholder wants to understand how a particular feature of our application should behave in a certain situation. However, tests tightly coupled to our code's intricate inner workings can't fulfill the role of serving as living documentation. Such tests lack the necessary readability.
Let's start with an example of a tightly coupled Application Test:
Keep reading with a 7-day free trial
Subscribe to Good Tests for Vue Applications to keep reading this post and get 7 days of free access to the full post archives.