Legacy Code to Testable Code #3: Adding Setter Accessors

Gil Zilberfeld's post in the "Legacy Code to Testable Code" on refactoring legacy code for unit testing and unit tests.
Standard

This post is part of the “Legacy Code to Testable Code” series. In the series we’ll talk about making refactoring steps before writing unit tests for legacy code, and how they make our life easier. Other posts include:

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

Adding accessors to private state data is an admission that either our design is wrong, or that we’re adding the accessors purely for unit testing. If that state was private before, how come are we exposing it? And if so, maybe unit tests are not the only clients of this data.

We need an accessor in two cases:

  • When we need to inject values that are otherwise hard to inject
  • When we need to probe the internal state to understand the impact of our operation

In both cases, we’re adding functions that were not needed before. Meaning, the caller did not need to either inject or probe the data. Now our unit tests, another client, demand an additional entry point. In this post, I’ll concentrate on the Set accessors for injecting values.

Injecting values

If we have other methods, we might not need an accessor. For example, we have a private field that’s initialized in the constructor:


If we pass the new bank value as a parameter, we can set it in our unit tests easily to whatever we need. However, if the constructor looks like this:


The seam for inserting our bank is no longer there, and we need another way to inject it. In another case, the new bank can come from an external source:


In both of these cases, we can mock the creation or the static call with power tools. If we can’t use those, we can introduce a “setter” method. This “setter” can be public or at least accessible to our test.


When we call this method, it overrides the value that the constructor initialized. This doesn’t work if our constructor already did something with the original value. Usually it’s not a problem, because we’re unit testing code in another method, so whatever the constructor did, the tested method will be under our influence. In other cases, we can mock the constructor using partial-mocking for eliminating code that runs in the constructor, or use power tools to mock the factory method.

From here it gets complicated. What if our bank is static? Consider a private static singleton:


Once initialized, it cannot be replaced through regular means. If our unit test calls for it, replacing it can be easier by injecting our own mockBank. So we can add a static “setter”:


As before adding an external “setter” is a risk: What if someone else decides to call it? We can reduce accessibility, but the risk is still there.

In both former cases, we could store the overriding mockBank. It maybe that the value is not stored anywhere, only created on the stack, in the tested method:


Here, we cannot access tempBank, because it’s on the stack. In this case, we can do a bit of refactoring in order to allow injection. We’ll first extract the creation into a private method called getBank:


Using refactoring tools, we can reuse this method for every creation. We can then introduce a field and a “setter”, and modify the getAccount method:


Using this method we skip mocking (and reduce coupling) at the expense of design changes. We can’t escape trade-offs.

There’s a final option for injecting values, if the language supports it: Reflection.  If we use reflection in the unit test, there is no need for modification in the tested code. However, unit tests using reflection are as fragile as mocking unit tests, sometime even more so. Reflection access is fragile, and is not risk-free refactoring. Therefore, this method should be considered last in the solutions offered.

That’s it for now. Next time we’ll continue the discussion about accessors with “getters”.

Image source: http://en.wikipedia.org/wiki/Setter

Leave a Reply

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