How to unit test a Java class with static initializers
We have a large Java codebase that we're trying to put under test. Since this class was not designed for testability we often run into code that cannot be tested as is. Most often this involves code that assumes it will always be run inside our J2EE Application Server because it depends on classes provided by the server.
I was recently working with someone on this Java class
class Person {
static {
Logger logger = new ContainerLogger();
}
public String doSomething() {
...
logger.info("I did something");
...
}
}
We couldn't create a unit test for the doSomething method because the ContainerLogger class was provided by our application server and couldn't be used outside the container. We refactored to come up with this solution.
class MyClass {
public static Logger _logger;
public Logger logger() {
if (_logger == null) {
logger = new ContainerLogger();
}
return _logger;
}
public String doSomething() {
...
logger().info("I did something");
...
}
}
We refactored our code to access the logger using the accessor method logger() then we also made the logger variable itself public at the same time. Why did we do those two seemingly contradictory things? The accessor method implements the singleton pattern preserving the semantics of the static initializer and the public access to the instance variable _logger allows us to replace the implementation with a mock in our test.
public class TestMyClass extends TestCase {
public void testSomething() {
MyClass._logger = new MockLogger();
assert(MyClass.doSomething(), "expected return");
}
}
I've seen too much legacy Java code that assumes it will always run inside a container but with testing we need to change our mindsets because when running a test we will be outside the container. All dependencies on the container (and in most applications I've seen we don't need that many!) should be encapsulated and able to be mocked through dependency injection. If you do this you'll end up with simpler code that better follows the Single Responsibility Principle