Scaffolding a test template
When integration tests become too complex, one might opt to introduce a test template to hide boilerplate code. This can go from setup/teardown code, to technical assertion functions that have to retrieve information before a durable assertion can be made. There are many reasons why a test template could be introduced. What is important to keep in mind is that you should always understand what you are trying to achieve. Introducing a test template can help to unburden the test body of unnecessary code, but it can quickly go so far so that the test body only contains a single line without any context of what the test is trying to test.
In OOP languages, a test template can be created using a parent class that can be called from the child test class. In this example, a parent class holds the test fixture (HTTP client) and assertion function.
Child classes can benefit from this setup, as the parent class removes the need for any additional initialization. The result is a series of tests that only contain the necessary functional names.
The key thing to understand here is that the parent class does not hold the hand of the child class for the entire test. It only provides the necessary pieces to write a simple test. It is useful to provide the
protected methods in a set so that they can be used easily together. In this case, the assertion function directly uses the response of the
HttpClient so that the test does not have to read the response’s content first.
Extracting overloads and function alternatives
One of the common features of C# is method overloading. Usually, this is used for an alternative of the same functionality. Passing an additional argument can influence the behavior, but not enough that it deserves a new method name. Functional programs do not have ‘function’ overloads, as this would mess up the function currying. Alternatives are done via a new function name.
In both cases, the tests can sometimes quite feel like duplication – because it is. Testing simple overloading with an almost copy of the test can work in the short term or for a small number of overloads. Either way, it will cost you some technical debt that you have to repay at some point when a new overload is introduced or an alternative is added.
Let’s look at the following example. Here we are testing if the built-in
HttpClient returns the same response with different overloads.
Usually, you will spot such an opportunity because you will already have tried to refactor some common code out. In this case, the URL is extracted and sometimes you will see that the assertion is also extracted. These are all symptoms of a greater opportunity as it is the method overload that is the only thing different, not the stuff around it. OOP programmers may have trouble seeing this as this refactoring is about seeing functions as regular values. So, like any other refactoring, we can refactor the common things out.
This example is too small to see the full benefit but imagine you have 10+ overloads or a similar functionality that you want to test. As a functional programmer, you see duplication where OOP does not want to get involved, revealed by the complexity it takes to set this up with the rigid C# xUnit testing package.
Compare that with how functional programs handle the same thing. This example shows a similar approach in F# Expecto:
The ‘operations’ are just another list of functions that we pass to the test(s) that want to use them. The main goal here is to show that this refactoring is available in both languages, not to compete them against each other.
This post went over the possibility of extracting common test code in ‘unusual’ (for OOP) structures. This shows that functional programming is more than a play field for experimentation, and that it can be used to increase maintainability in your project. When thinking in an FP paradigm, you will see common patterns and structures that can be improved and refactored.
Free your mind, and you will see the full potential of clean and maintainable code.
Thanks for reading,
Subscribe to our RSS feed