Legacy Code to Testable Code #13: The Testable Object

Standard

Other awesome posts in the series:

The Legacy Code To Testable Code Series

General patternsAccessibilityDealing with dependenciesAdvanced patterns
IntroductionAdd settersExtract methodStatic constructors (initializers)
RenamingMore accessorsExtract classMore static constructors
Add overloadIntroduce parameterInstance constructors
Testable objectConditionals to guard blocks

If the code we want to test is highly-decoupled from its dependencies, we probably don’t need to do anything to it. But when we have a medium-big ball of mud things change. There are parts we want to ignore or control through mocking, and then there are parts of the object we’d like to keep.

Follow the patterns we’ve discussed so far (like extracting methods and classes) can help us separate the code into these parts. Then there are other techniques.

If you’ve done some manual mocking before, you already know how to do. A mock is created by deriving a type from the one we want to mock, and give the methods an alternate implementation.

What happens to the methods that we don’t “mock”? Depending on the implementation and the mocking framework we use, they either stay as is, or get mocked in a default way. When we use the first option, we call it “partial mocking”, because we intend to mock parts of the dependency.

But that’s what we were looking for in the original problem, just from the other point of view. We want to retain the interesting code, while mocking the other parts.

Enter the Testable Object

The idea is basically the same: Create a derived type from the tested type, make the parts that get in the way of testing more “testable”. And now you can run you tests.

Here’s an example:

The ZooKeeper class has a couple of external dependencies, and for these example, I’ve picked them to be static, and singletons and all kinds of issues. We can modify all the dependencies to be more mockable. But we’ll go the other way this time.

The private methods of ZooKeeper that call the external dependencies can be mocked with a bit of effort. We’ll change them from private to protected, and make them virtual (this example is in C#, so methods are not virtual by default).

Now our ZooKeeper looks like this:

It still has the problematic dependencies, but now we can create a testable object. We’ll create it in the test project. There’s a couple of extra accessors and logic there to help with behavior and assertions.

With this implementation, we’ll modify it to be able to answer the following tests:

Now the logic part is covered by our tests. Pretty simple, isn’t it?

Using inheritance comes with its own baggage. If for example the constructor of the object gives us problems, the testable object will continue with them. Only sometimes, you can override them by adding a constructor, only this time, on the testable object, rather than the object itself.

The benefit is that with quick modifications we’ve managed to isolate tested code from the problems in that same code. And that is also the weak point: The code still contains the problems. Refactoring may have helped in that matter. Of course, you can use them in combo.

Note to mocking power-tool users. Because power-tools don’t use inheritance, and can control any part of a mocked type, there’s no need to modify the class in order to mock it. For that matter, we don’t need to create a specific type just to test parts of the object. It is possible to do partial mocking on the regular types. Just remember to refactor to improve the code once you have tests.

Leave a Reply

Your email address will not be published. Required fields are marked *