TotT: Defeat "Static Cling"
Thursday, June 26, 2008
You're pair programming and, as many brilliant people are apt to do, talking out loud. "I'll make a mock, inject it, and rerun the test. It should pa- ...D'OH" Your partner notices the exception "ConnectionFactory not initialized". "What?" she says, "Something is using the database? Dang, and this was supposed to be a small test."
Upon inspection you find that your class is calling a static method on some other class. You've got Static Cling! If you're (ab)using a data persistence layer that generates code which relies on static methods, and weren't careful, your code might look something like this:
As a result, you can't call doSomething without calling TheirEntity's static method. This code is hard to test because static methods are impossible to mock in Java.
So, how do you get rid of this form of Static Cling and get that small test to pass? You can use a technique sometimes known as the Repository Pattern, a form of Abstract Factory. Create an interface and an implementation with the unmockable static method calls:
Next, inject a TheirEntityRepository into MyObject and use it instead of calls to the static method:
You can do this even if you don't have access to source code for TheirEntity, since you're not changing the source itself, but merely encapsulating its static methods in an injectable interface. The techniques shown here generalize to the case where a static method acts as a Factory of objects.
Now you can inject different implementations of the repository for different tests, such as "never finds anything," "always throws an exception," "only returns a TheirEntity if the id is a prime," and so forth. These kinds of tests would've been impossible before this refactoring.
Upon inspection you find that your class is calling a static method on some other class. You've got Static Cling! If you're (ab)using a data persistence layer that generates code which relies on static methods, and weren't careful, your code might look something like this:
public class MyObject {
public int doSomething(int id) {
return TheirEntity.selectById(id).getSomething();
}
}
public int doSomething(int id) {
return TheirEntity.selectById(id).getSomething();
}
}
As a result, you can't call doSomething without calling TheirEntity's static method. This code is hard to test because static methods are impossible to mock in Java.
So, how do you get rid of this form of Static Cling and get that small test to pass? You can use a technique sometimes known as the Repository Pattern, a form of Abstract Factory. Create an interface and an implementation with the unmockable static method calls:
interface TheirEntityRepository {
TheirEntity selectById(int id);
// other static methods on TheirEntity can be
// represented here too
}
public class TheirEntityStaticRepository
implements TheirEntityRepository {
public TheirEntity selectById(int id) { // non-static
return TheirEntity.selectById(id); // calls static method
}
}
TheirEntity selectById(int id);
// other static methods on TheirEntity can be
// represented here too
}
public class TheirEntityStaticRepository
implements TheirEntityRepository {
public TheirEntity selectById(int id) { // non-static
return TheirEntity.selectById(id); // calls static method
}
}
Next, inject a TheirEntityRepository into MyObject and use it instead of calls to the static method:
public class MyObject {
private final TheirEntityRepository repository;
public MyEntity(TheirEntityRepository arg) {
this.repository = arg;
}
public int doSomething(int id) {
return repository.selectById(id).getSomething();
}
}
private final TheirEntityRepository repository;
public MyEntity(TheirEntityRepository arg) {
this.repository = arg;
}
public int doSomething(int id) {
return repository.selectById(id).getSomething();
}
}
You can do this even if you don't have access to source code for TheirEntity, since you're not changing the source itself, but merely encapsulating its static methods in an injectable interface. The techniques shown here generalize to the case where a static method acts as a Factory of objects.
Now you can inject different implementations of the repository for different tests, such as "never finds anything," "always throws an exception," "only returns a TheirEntity if the id is a prime," and so forth. These kinds of tests would've been impossible before this refactoring.
It is possible to have two classes in Java with the same fully qualified class name, if they are loaded by different class loaders. I don't know how practical it would be to take advantage of that, but at least theoretically it would be possible to mock a static method.
ReplyDeleteThere is also the technique suggested by Michael Feathers in "Working Effectively with Legacy Code" whereby you isolate the offending calls in a handful of protected methods. You then subclass the class under test and stub out these methods.
ReplyDeleteYou could "mock" the static method with JMockit. However, the pattern suggested here seems like a much better solution.
ReplyDeleteFor .NET programmers, I've written some code that will generate an assembly with the described interface declaration and forwarding implementation. For more information, refer to the testing library http://jolt.codeplex.com.
ReplyDelete