Software Development

Refactoring to Allow Test Doubles

Sometimes, when you make a class, it directly instantiates an object to use in its methods. For example:
 
 
 
 
 
 
 
 
 

public void myFunc()
{
   MyType object = new MyType();
   object.doSomething();
   this.thingy = object.getSomething();
}

This is generally viewed as bad, since you’re tightly coupling your class to the instantiated one. “But,” you say, “I know that I will never use any other class, and I certainly don’t want users of the class to be supplying alternatives to use.” These concerns are mostly valid, but they leave out a very important thing to pay attention to: testing.

You should be able to replace that object with a test double (often mistakenly called mocks; mocks are a specific type of double), for all the usual reasons for having doubles. But, as is, you can’t replace it. Doing so would allow all users of my class to do it too, wouldn’t it?

What if I told you that you could have your cake and eat it too? (such a strange phrase…) Well, you can, and I’ll give you a nutshell explanation about how, then go into the details.

Keep in mind, it doesn’t require any sort of annotations or DI frameworks (I’m not a big fan of trusting “magic” to do what I should be doing; I have nothing against those who do, but I just don’t like doing it).

The Nutshell

Assuming you’re doing your tests the typical way of having them in the same package as what they’re testing but in a different directory, you can actually very easily set something up to replace your “unchangeable” object with a test double. All you need is a package-private (default access) way to supply the class with the double and a private way to access it. Then you can use the package-private calls in your tests to supply the class with the test double.

There are a few ways to do each part of it, so I’ll dig into that.

Suppliers

I can think of three different ways to tell the object being tested what double to use, and it turns out that they’re exactly the same three basic ways of doing typical dependency injection:

  1. Constructor
  2. Setter (aka Mutator)
  3. Field

You may notice that I used numbers instead of bullet points. This is because I consider some to be superior to others. You should consider each method in the order that they appear.

Constructor Injection

Make a package-private constructor on your class that accepts the normal parameters, plus the test double. Then your existing constructor(s) and/or static factory method(s) can delegate to that constructor with the default object.

This is often the best option, especially for immutable types. By passing it in through the constructor, the object will not need to change, which immutable types aren’t supposed to do. Granted, passing it in later doesn’t break the class’ effective immutability, since the changes you are applying aren’t possible in the production code, but it removes the “awesome side benefit” that I mention at the end of the article.

Using the constructor also reduces the number of lines required in your tests, since it is a line that you’re already going to be typing.

The only real downside to constructor injection is that it doesn’t allow the ability to change out the test double without creating a new object. This is rarely a problem, though, since you usually don’t need more than one per test, and each test should be creating a new instance to ensure test independence.

Setter Injection

Make a package-private setter method that the test can call to set a field to the test double. The constructor sets the field to a default or null (the technique for accessing the test double can test for null and use the default if it is).

This isn’t a horrible option, but, as stated above, is definitely not ideal for immutable classes. The best part of it is its obviousness. Seeing the setter being called makes it clearer about what the test double is doing there, but not usually a lot clearer. Its other benefit, as the previous section states, is the ability to change the double out without creating a new instance of this class. Again, this isn’t all that helpful, really.

Field Injection

This is done by making the field that stores the double package-private and directly assigning the double to it.

You pretty much shouldn’t do this, for the same reasons you don’t make fields public. You may think that it’s harmless to do this, since it’s only for testing, but it removes the “awesome side benefit” at the end of the article.

Technically, it’s an option that works, but that’s the only reason I even bothered to include it in the article. It’s bad design; avoid it at all costs. Please.

Accessors

The accessors are almost always private. This is usually because we didn’t even want them until we started to refactor our code to use the test doubles. I can only think of two types of accessors, which are mirrors of the suppliers. Since you can’t do a mirror of the constructor, there are only two:

  1. Getter
  2. Field

Again, the two are in the preferred order, although the difference between how much I prefer one over the other is significantly less.

Getter Access

Make a private getter method that returns the test double or the default object, which any code that originally used the default object can call to get their object.

Truth be told, I find very little benefit to using a getter over direct field access. The biggest help it provides is when you have the field set to null to signify to use the default, which, in itself, is not usually the best design, since a null check is slower than straight access. There is a time where this might not be the case, which is brought up in the section, Instances and Factories.

Field Access

Just use the private field when you need to use the double or the default. It’s really simple.

This is okay as access largely because it’s only happening within the class, which is allowed to use its own implementation details.

Overload Wrapping or “Hidden Call Injection”

This technique ignores the idea of using package-private suppliers and private accessors and works in a very different way, not even needing the addition of a field to the class. It’s especially useful if only a single method uses the object that needs a double (though, that situation might suggest that your class is breaking the SRP).

The technique involves overloading the method you want to test with a package-private method that accepts the dependency as a parameter. You then move all the logic from the original into the overload, replacing the locked-in dependency with the one passed in by the parameter. Then the original method simply calls the new overload and passes in the original object. For example, you can turn this

public void myFunc()
{
   MyType object = new MyType();
   object.doSomething();
   this.thingy = object.getSomething();
}

into this

public void myFunc()
{
   MyType object = new MyType();
   myFunc(object);
}

void myFunc(MyType object)
{
   object.doSomething();
   this.thingy = object.getSomething();
}

Now you can pass in test doubles and isolate the functionality of myFunc().

Instances and Factories

There is one thing you’ll have to watch out for when refactoring like this: how often new instances are needed. If you need a new instance of the depended-on object every time you run a function, then all suppliers need to pass in a factory for creating the object instead of instances of the object. With overload wrapping, you can usually pass in single instances, unless the individual method itself makes multiple copies of the object.

If you haven’t read it yet, I wrote a post a little bit ago about making simple functional factories in Java and Python.

Test the Defaults

Don’t forget: you still need to test whether the it works with your default instance, too. If it runs slowly, I’d put it into your integration tests.

Disappointing Side Effect

Your tests are supposed to be living documentation about how your class can be used. Using these techniques, unfortunately, teaches the reader about ways that aren’t actually available to them. Maybe, just maybe, you should consider not being so stubborn about what specific class is used inside?

It’s totally up to you; I was just providing a disclaimer (and a hint :) ).

Awesome Side Benefit?

One really cool side-effect of refactoring like this is the fact that, if you ever change your mind and want to allow your code users to inject their own versions of the dependency, all you need to do is set your suppliers and overloads to public.

In fact, you can take this entire article and replace every instance of “package-private” with “public” to make it a general article about providing dependency injection.

Reference: Refactoring to Allow Test Doubles from our JCG partner Jacob Zimmerman at the Programming Ideas With Jake blog.

Jacob Zimmerman

Jacob is a certified Java programmer (level 1) and Python enthusiast. He loves to solve large problems with programming and considers himself pretty good at design.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button