Testing the untestable with the Adapter Design Pattern
During my session at last weeks Codegarden on ‘Getting started with Unit Testing in Umbraco’ I got a really good question in the Q&A afterwards, and I thought I would share the question and elaborate on my answer:
-“What tips do you have for mocking services that are overly complex or have lots of dependencies that can't be null?”
I’m assuming that the person who asked this question are referring to external services that can’t be refactored or rebuilt. Because if it’s your own services that are overly complex to mock I would definitely look in to why they are so complex and have them refactored to make them easier to test/mock.
But with external services we don't really have that luxury and unfortunately not all external services have been built with testability in mind.
-"Not all services have been built with testability in mind."
Lets say you use a service from any of your packages (lets say Umbraco, since I’m assuming the majority of people reading this blog are Umbracians) and for whatever reason it seems impossible or very complicated to mock.
Maybe it has an internal constructor that can’t be mocked (let's ignore reflection hacks for now). Maybe it uses the static service locator directly in the class instead of using dependency injection or it might be heavily dependent on an HttpContext, UmbracoContext or WhateverContext..
What do you do?
Hello Adapter!
So what I usually do in these situations is use the Adapter Design Pattern. There is a lot of different ways you can use this pattern and I’m not going to talk too much about what it is because it has already been covered very well by someone much smarter than me:
How the Adapter Design Pattern Can Make Your Life Easier!
(In this tutorial the author mentions four examples of how to use this pattern, but we are primarily focusing on the first example.)
A complex service example:
Imagine you have a controller that uses an external service called ComplexService (in lack of imagination) and for whatever reason this service is impossible to mock. (Note that these examples are in plain .NET, not Umbraco specific.)
So what you do is you create an Interface which has the same methods defined as the ComplexService, which we’ll use later in our Adapter. For this example we'll call it IComplexService:
Now the magic happens! Now we create our Adapter (which is basically just a fancy word for wrapper) and we let it inherit our newly created interface. Lets call it ComplexServiceAdapter. As you can see in this example, all this class really does is forward the requests to the same methods in the underlying ComplexService.
Then we need to register the ComplexServiceAdapter as an IComplexService in our IoC container to be able to inject it in our previously mentioned controller. (If you are using Umbraco, have a look at Composers if you're not sure how to register dependencies in the IoC container.)
And that’s it! Now we just have to update the controller to use the IComplexService instead of the concrete ComplexService (so basically we just need to add the letter ‘I’ in front of it).
The beauty here is that since all the methods and parameters in the IComplexService interface matches the methods in the ComplexService, we don’t need to rewrite anything else in the rest of our controller.
Now we have an interface to mock in our tests instead of an overly complex service, and interfaces are super easy to mock! (If you are using Moq, it's as easy as new Mock<IComplexService>()).
Two takeaways:
- The original question was how to mock complex services and using this approach you haven’t really mocked the complex service, you’ve worked your way around it. You should always try to find a way to mock concrete services either by yourself or by reaching out to the author of the package first. Consider this approach your last resort when all else fails.
- In my Codegarden session I mentioned "Don’t test Umbraco, test how your code interacts with Umbraco." and I think this is a good example of that. In this example we are not actually testing the complex service, we are testing how our controller interacts with the behaviour of the complex service, which is a big difference.
Hope it can be useful or help someone out in the future who get’s stuck trying to mock an overly complex service.
Cheers friends! ❤️