What makes “testable code”? And how does dependency injection ties in?
Let’s start with an axiom. Everything is testable. It’s just a matter of motivation, effort and skill.
The first one is the most important: If we feel the code is “untestable”, we won’t even try to write tests. That’s where most people give up.
Let’s take it down a notch. We know that it’s easier to write tests for code that doesn’t have dependencies. If there are no dependencies, the code doesn’t come with baggage – the setup for each test is smaller, and there are less side effects between tests.
But, we don’t write all code from scratch. Most code uses frameworks, libraries, derive from base classes and carry some kind of dependency baggage.
So, the next step is to break down and separate these dependencies. There are a couple of ways to do this. Today I’m going to talk about dependency injection.
It’s time for some injection
Let’s take a look at this code:
public LocalDate yesterday()
{
LocalDate localdate = LocalDate.now();
return localdate.minusDays(1);
}
Code language: Java (java)
It has temporal dependency. Meaning, whenever we run it (also, wherever we run it), it will return a different answer. That makes it hard to test.
The solution is to control the localdate. One way is to change the code to accept it as parameter:
public LocalDate yesterday(LocalDate localdate)
{
return localdate.minusDays(1);
}
Code language: Java (java)
Now, a test can call the method with any localdate value.
Another way we can pass a dependency, since the now() method can be controlled by passing in a Clock object. This time we can control the Clock, instead of the localdate.
public LocalDate yesterday(Clock clock)
{
LocalDate localdate = LocalDate.now(clock);
return localdate.minusDays(1);
}
Code language: Java (java)
These methods use dependency injection, or as we call it sometimes, “pass in a parameter”. That’s not the only way to inject dependencies. We can use a dependency injection a Factory or a Service Locator to achieve what we want: separation between the creation of the dependency, and its use by the tested client.
There are other solutions, leaving the code as-is.
For example, we can use Mockito (or any other capable mocking framework) to mock the static method LocalDate.now() method. I’ll discuss the pros and cons of this method in the future, but even using a mocking framework like that, we inject a controlled dependency. Just not in the original definition.
Finally, here’s a cool trick
What if our class looks like this:
public class Days {
private LocalDate localdate = LocalDate.now();
public LocalDate whichDay() {
// ... do some day calculation
return yesterday();
}
public LocalDate yesterday()
{
return localdate.minusDays(1);
}
}
Code language: PHP (php)
In this case, the localdate is not a locally created variable, but is a field. It seems we’re back to our original problem. Funny thing, the original solution still works. Modifying our tested method, makes it easily testable:
public class Days {
private LocalDate localdate = LocalDate.now();
public LocalDate whichDay() {
// ... do some day calculation
return yesterday(localdate);
}
public LocalDate yesterday(LocalDate localdate)
{
return localdate.minusDays(1);
}
}
Code language: PHP (php)
You don’t have to do that, because the method knows the class’ field. But by changing the signature, we can now inject a controlled dependency, and bypass the problematic one.
Using dependency injection in different creates easy ways to separate dependencies, and write the tests for our code with ease.
I’m discussing this, and other testability patterns in my clean code workshop. Check it out!
0 Comments