In the first post about the basics of the Testing API, I talked about how a Testing API can help you decrease the structural coupling between your production code and your test code. In the long run, it will save you much time! A bold claim, of course, and it sounds very theoretical. To provide you with more depth than just a basic concept, I want to show you today how you can implement a Testing API with .NET. Therefore I want to look at the following aspects in more detail today:
If you need more information about the basics of a Testing API and what it's good for, check out my first post of this series!
In my first post, I mentioned that the origins of the Testing API come from the book "Clean Architecture" by Robert C. Martin. He describes this Testing API as follows:
API that the tests can verify all the business rules. This API should have superpowers that allow the tests to avoid security constraints, bypass expensive resources (such as databases), and force the system into particular testable states. This API will be a superset of the suite of interactors and interface adapters that are used by the user interface.
So ultimately, your tests call the API, and it provides everything the test needs to verify the business rule.
The main question is, if you don't have a "Clean Architecture," is it even possible to integrate the Testing API? For that, let's look at layered basic architecture.
The above architecture contains four components: the UI, the Database, the business logic, and the tests. Usually, you want to test your business logic by directly creating concrete instances of classes from the business logic. The concrete instantiation, of course, leads to tight structural coupling between your production code and your test code.
Thus, the question is, how would you fit a Testing API into this architecture? Why not simply integrating another component between your business logic and your tests?
You might already know this picture from my first article about the Testing API. The approach is still valid, however. The Testing API contains everything that you need to verify your Business Rules. Your Tests, no matter what kind of tests, only access your API. In the end, the Testing API component gets all the necessary information from the business logic.
So you might say: Okay, we already know that, but this is only a theory. So let's see how you can implement your Testing API with .NET.
Before we look at the implementation, imagine creating an app for a car repair shop. The software has the following requirements:
We can easily use these requirements for our Testing API. I created a new .NET class library for our Testing API and added three classes to it:
Let's assume we've already implemented the production code and some tests. You can find the source code on GitHub, so you can easily follow along with this article.
Eventually, our .NET solution for our car repair shop app looks like this:
The CarRepair.Core and CarRepair.Application project contains all the entities, aggregates, and, in general, the application business logic. The CarRepair.Test project consumes the information only from the CarRepair.TestingApi project. Therefore our tests' structures are independent of the design of the production code. The independence gives us higher flexibility when refactoring our code.
The above dependency diagram shows you what the main dependency paths are. The bigger the blue arrows are, the more they depend on another component. As you can see, our tests mainly rely on the Testing API, and our API only depends on the application business logic. So your Test project does not directly connect to your Application project, and thus they are structurally more independent.
Let's write one test to consume our Monitoring class from the Testing API.
The Test class uses the MSTest framework, and our test is relatively simple. We want to get the correct repair status information if the car shop updates the repair status. Instead of building a concrete instance of a class that sends the update, we create a new instance of our Monitoring Testing API.
The Monitoring class provides methods which we need to verify our business rules. The Notify method creates a new instance of an observer in those methods, which sends the latest status update. So if something changes to the structure of the RepairStatusObserver class, we don't need to worry about that in our tests, significantly if the business rules don't vary.
The GetCurrentRepairState() method needs to do a little bit more to return the current RepairState object. At first, it authenticates the user, creating an instance of the MonitoringNotificationReader for reading the new monitoring status. Then, a parser parses the description of the monitoring status into the RepairState. The Monitoring Testing API effectively encapsulates everything for us, and it hides the internal structure. If anything changes to the authentication process or how we get the RepairState object, we only need to adjust the code inside the Testing API. As long as your business rules stay the same, your tests don't change. It is as simple as that!
In general, there are no rules which tell you how to structure your Test project or even your Testing API project. Everything is very flexible, and you can structure it in every way you prefer. You can also have multiple Test projects, but it is easier to have only one Testing API project.
So how could the structure of your Test project look?
One way is to have one Test project, and it contains folders for every kind of test. This folder can have, of course, further subfolders, depending on your domains and your application business logic.
As already mentioned in my first blog article, they can also leverage the Testing API if you write Unit Tests. But it blows up your API. Usually, you write Unit Tests to verify the logic inside public methods (a unit). Therefore, in this case, it makes sense to access the Application project directly. Consequently, it is better to separate your concerns to create another Test project just for your Unit Tests. You can keep all the other tests, like Integration Tests, System Tests, or even End-to-End Tests in the Test project, as we see in the above picture.
Implementing a Testing API in .NET is pretty simple. Leveraging the power of this concept takes more effort and thought into designing the API. But as you have seen in the above example, if implemented correctly once, the Testing API helps most of your test code be independent of the structure of your production code.
Here are the key takeaways:
What do you think about this approach? I'm curious to see what you think about writing Testing APIs with .NET. So leave a comment down below and let me know if you liked this article.
Structural coupling makes refactoring our code very difficult and it costs us a lot of time and money! But there is one solution: a Testing API. It's an API with superpowers which helps you to get rid of this structural coupling. At the same time you are still able to verify your business rules. This can be a real time-saver!
If you want to bring your API to production fast, Azure Functions are a good choice for that. In this post, we look at how we can implement REST APIs with Azure Functions which are highly scalable and easily maintainable.
Azure DevOps is a great tool for tracking the progress of your work, version control, and CI/CD. But how can you bring it together with Scrum? Especially Backlog Refinement events? In this post, I want to show you the customization power of Azure DevOps to leverage its feature to improve your Backlog Refinement events.