So let’s just dive in today. Our current test class is fairly clean and straightforward, but we’re not using all of Moq’s capabilities yet. One notable thing is that we have to remember to call VerifyAll on all of our individual mocks. Fortunately, Moq provides us with a tool to mitigate this problem: MockRepository is a class we can use to create and track mocks, and verify all expectations at once.
In the real world, Factories manufacture items for output. In software engineering, Factories create object instances for us so we can avoid using the “new” operator. Factories also give us substantially more control when testing, and can even provide a means of verifying “Black box” operations.
Why would we want to avoid the “new” operator? Isn’t that what it’s there for?
Well, yes, but newing up a dependency within a class goes against the concept of dependency injection. Thinking back to our ReallyHardToTest class, we know we’re creating a new instance of SomeModel in the constructor, and one of our methods provides a way to change the SomeModel instance. The problem is that since we create the instance within ReallyHardToTest, we cannot control it or even verify that an instance is created. Furthermore, we can’t verify that the SetFilename method actually does what it says on the tin because the SomeModel instance isn’t exposed publicly by that class.
Factories Help Us Build Things
At its root, the factory pattern (there are actually a few variants) encapsulates the logic for creating instances of particular types. When employing these patterns, now dependent classes can have a factory injected, and then ask the factory for a new instance when they need it. Since the factory is injected, we can control it at test time – we even get to control the instance returned by the factory!
In advanced scenarios, for example when a factory will be invoked repeatedly, we can use advanced features of Moq, including SetupSequence() to return different instances in a specific order.
Programming to Abstractions is a another common architectural pattern that facilitates unit testing. Abstractions provide a way for us to decouple specific implementation details from the consuming class. Another benefit of abstractions is that we can substitute alternate implementations if needed – something that is very difficult to do otherwise.
Let’s start by looking at our trusty ReallyHardToTest class that we’ve been working with:
Let’s focus on the ReallyHardToTest constructor, which takes an instance of SomeOtherDependency. We know from prior tutorials that we can’t mock concrete types, but we want to make sure that the ReallyHardToTest constructor invokes SomeOtherDependency.DoSomething(). So how can we make this more testable?
Challenge: Classes with Static Dependencies are Difficult to Test
Static Dependencies pose a set of challenges to testability, most notably:
- Static Dependencies cannot be mocked
- Due to their nature, static dependencies are rarely injected so they are hidden dependencies
In my prior post, I showed a very difficult-to-test class that uses the static System.IO.Path.GetFileName method. Today, we’ll do some refactoring on that class to make it more testable!
My students often ask me “How can I tell if my code is too complex?” There are a lot of different answers to that question, but I thought I would share my favorite complexity detection tool with you – a tiny purple stuffed monkey:
The basic concept behind it all is to explain your code to your duck/monkey/other inanimate object, step by step. If it takes you longer than 1-2 minutes to explain a method, it’s probably too complex and should be refactored to something simpler.
Well that sounds great and all, but how exactly do I reduce a method’s complexity?
Good catch – “Just simplify it” is unacceptably hand-wavey and doesn’t really provide much guidance. One good approach for reducing complexity is called the Single Level of Abstraction Principle (SLAP). As might be apparent from the name, SLAP recommends that all statements within a given method exist at the same “Level of Abstraction”.
OK, but what’s a Level of Abstraction?
Testability is a key concern when following TDD practices. There are many different ways to accidentally write difficult-to-test code, but fortunately they tend to fall into fairly broad categories. In this post, we’ll examine some common coding pitfalls that introduce testing challenges, and future installments will discuss ways to work around or avoid these issues.
So What Do You Mean By Testability?
Testability is a measure of how easy (or difficult) it is to write tests for a given class. There are several development practices and patterns that work together to improve a given class’ testability, including Dependency Injection and SOLID Principles. In general, highly-testable classes will have several traits in common:
- Dependencies are specified as abstractions (either interfaces or abstract classes)
- Dependencies are injected rather than created internally
- Factories are used to create objects with runtime dependencies
- Avoidance of static classes and methods wherever possible
So let’s take a quick look at a class that is inherently difficult to test and identify the various problems…