We learned about testing impediments and how to refactor them. We cannot unit test code when testing impediments are present; we refactor the code and move the impediments out (to another class or methods), and during testing, the impediments are replaced with mock objects.
However, sometimes we cannot mock out the external dependencies because of testing an unfriendly design. This section covers the design for testability, or rather matters to avoid in code. The following Java constructs go up against mocking the testing impediments:
private
methodsfinal
methodsstatic
methodsfinal
classesnew
You cannot unit test the legacy code because either it is tightly coupled or testing unfavorable language constructs hide the testing impediments. The following section explains the testing unfavorable constructs.
To build a test, we need to instantiate the class in the test harness, but the problem with legacy code is that it is difficult to break dependency and instantiate a class in a test harness. One such example is in a constructor, where the class instantiates many objects, reads from the properties file, or even creates a database connection. There can be many callers of the class, so you cannot change the constructor to pass dependencies; otherwise, it will cause a series of compilation errors.
We will take a look at a sample legacy code and try to write a test for the class.
Suppose we have a TestingUnfavorableConstructor
class with two external dependencies DatabaseDependency
and FileReadDependency
. Both the dependencies are slow in nature and are testing impediments. TestingUnfavorableConstructor
creates dependencies in the constructor. Ideally, the dependencies represent the database access and the file reads from the TestingUnfavorableConstructor
constructor. The following is the TestingUnfavorableConstructor
class:
public class TestingUnfavorableConstructor { private DatabaseDependency dependency1; private FileReadDependency dependency2; public TestingUnfavorableConstructor() { this.dependency1 = new DatabaseDependency(); this.dependency2 = new FileReadDependency(); } public Object testMe(Object arg) { return arg; } }
If we want to unit test the testMe()
behavior of the class, then we need to create an object of the TestingUnfavorableConstructor
class. However, when we try to create an instance in a unit test, the class fails to indicate that the class cannot be instantiated from an automated test suite. The following is the output:
To overcome this, you should inject the dependencies through a constructor instead of creating them in a constructor.
We cannot modify the default constructor because the class is invoked from many other clients. We cannot break the clients. The other two options are as follows:
The first option is relatively straight forward. We'll apply the second approach.
The following is the modified code:
public class TestingUnfavorableConstructor { private DatabaseDependency dependency1; private FileReadDependency dependency2; public TestingUnfavorableConstructor() { createDependencies(); } protected void createDependencies() { this.dependency1 = new DatabaseDependency(); this.dependency2 = new FileReadDependency(); } public void setDependency1(DatabaseDependency dependency1) { this.dependency1 = dependency1; } public void setDependency2(FileReadDependency dependency2) { this.dependency2 = dependency2; } public Object testMe(Object arg) { return arg; } }
The following unit test overrides the TestingUnfavorableConstructor
and provides an empty implementation of the createDependencies()
method, creates mock dependencies, and calls setter methods to set the mock dependencies:
@RunWith(MockitoJUnitRunner.class) public class TestingUnfavorableConstructorTest { @Mock DatabaseDependency dep1; @Mock FileReadDependency dep2; TestingUnfavorableConstructor unfavorableConstructor; @Before public void setUp() { unfavorableConstructor= new TestingUnfavorableConstructor() { protected void createDependencies() { } }; unfavorableConstructor.setDependency1(dep1); unfavorableConstructor.setDependency2(dep2); } @Test public void sanity() throws Exception { } }
Do not instantiate dependencies in the constructor; the dependencies may exhibit testing impediments and make the class nontestable. Instead of instantiating the dependencies in the constructor, you can pass the real implementations (real dependencies) to the constructor or the setter method of the code under the test.
Class-level variable declaration and object instantiation at the same time creates problems. You don't get the chance to mock out the variable. The following example explains the problem:
The VariableInitialization
class has a database dependency, and the dependency is instantiated where it is declared, as follows:
Public class VariableInitialization {
DatabaseDependency dependency1 = new DatabaseDependency();
public void testMe(Object obj) {
}
}
When you instantiate the VariableInitialization
class in test, the test fails. The following is the output:
The following is the test class:
public class VariableInitializationTest { VariableInitialization initialization; @Before public void setUp() throws Exception { initialization = new VariableInitialization(); } @Test public void sanity() throws Exception { } }
To overcome the class-level variable initialization, you can try out the following options:
The private
methods are useful for hiding the internal state and encapsulation, but they can also hide the testing impediments. The following example explains the details:
The PrivateMethod
class has a private
method named showError()
. This private
method hides a test impediment. When we unit test the validate()
method with a null
object, the validate()
method calls the showError
message, as follows:
public class PrivateMethod { public Object validate(Object arg) { if(arg == null) { showError("Null input"); } return arg; } private void showError(String msg) { GraphicalInterface.showMessage(msg); } }
The following is the test output:
You can extract the testing impediments to a protected method, or you can separate the concern. Create a new class, move the testing impediment to that class, and inject the new class as a dependency.
The following code refactors the testing impediments and makes the class unit testable:
public class PrivateMethodRefactored { public Object validate(Object arg) { if(arg == null) { showError("Null input"); } return arg; } protected void showError(String msg) { GraphicalInterface.showMessage(msg); } }
The showError
method's access specifier is changed to protected
.
The following test code extends the class with an anonymous implementation, and it overrides the protected method with an empty implementation. The test code invokes the validate()
method on the new anonymous implementation of the PrivateMethodRefactored
class. In turn, the polymorphic behavior will call the empty implementation. Hence, the test will always bypass the testing impediments by calling the overridden empty implementation of the testing impediment, but the real production code will always invoke the protected method:
public class PrivateMethodRefactoredTest { PrivateMethodRefactored privateMethod; @Before public void setUp() { privateMethod = new PrivateMethodRefactored() { protected void showError(String msg) { } }; } @Test public void validate() throws Exception { privateMethod.validate(null); } }
This approach of bypassing the testing impediments with overridden versions of the testing impediments is known as faking or fake object. If the code under test contains many testing impediments, then it is not possible to override all of them in an anonymous class. Instead, we can create an inner class, and extend the code under test and override all the testing unfriendly methods.
When a method is final, you cannot override it. If the final method hides any testing impediment, you cannot unit test the class. The following example explains the issue:
The FinalDependency
class has a final method named doSomething
. This method hides a testing unfriendly feature. The following is the class definition:
public class FinalDependency { public final void doSomething() { throw new TestingImpedimentException("Final methods cannot be overriden"); } }
The FinalMethodDependency
class has a dependency on FinalDependency
, and in the testMe
method, it calls the doSomething
method as follows:
public class FinalMethodDependency { private final FinalDependency dependency; public FinalMethodDependency(FinalDependency dependency) { this.dependency = dependency; } public void testMe() { dependency.doSomething(); } }
In the test, we'll mock the dependency and unit test the code as follows:
@RunWith(MockitoJUnitRunner.class) public class FinalMethodDependencyTest { @Mock FinalDependency finalDependency; FinalMethodDependency methodDependency; @Before public void setUp() { methodDependency = new FinalMethodDependency(finalDependency); } @Test public void testSomething() throws Exception { methodDependency.testMe(); } }
When we run the test, the test still accesses the testing impediment, as the mock object cannot stub a final method. When we try to stub the method, we get an error. The following test stubs the final method call:
@Test public void testSomething() throws Exception { doNothing().when(finalDependency).doSomething(); methodDependency.testMe(); }
When we run the test, we get the following error message thrown by the Mockito framework:
The only possible way to overcome this is extracting the content of the final method to a protected method; call the protected method from the final method, and override the protected method in test. Otherwise, you can use the PowerMock or PowerMockito framework if you cannot touch the class at all; for example, when you only have a JAR file.
The static
methods are good for utility classes, but unnecessary use of static
can hide the testing impediments and create problems in unit testing. The following example sheds light on the issue:
The SingletonDependency
class is an implementation of the
Gang of Four (GoF) singleton design pattern. It has a private
constructor and a static getInstance()
method to create only a single instance of the class. The static callMe()
method hides a testing impediment. Note that the GoF singleton pattern doesn't define methods as static
, but in this example, we are defining the callMe()
method as static
to display a drawback of the static
methods. The following is the singleton implementation:
public class SingletonDependency { private static SingletonDependency singletonDependency; private SingletonDependency() { } public synchronized static SingletonDependency getInstance() { if (singletonDependency == null) { singletonDependency = new SingletonDependency(); } return singletonDependency; } Public static void callMe() { throw new TestingImpedimentException("we dont need singleton"); } }
The VictimOfAPatternLover
class has a dependency on SingletonDependency
. The following are the class details:
public class VictimOfAPatternLover { private final SingletonDependency dependency; public VictimOfAPatternLover(SingletonDependency dependency) { this.dependency = dependency; } public void testMe() { dependency.callMe(); } }
Mockito cannot stub a static method. When we try to stub the static callMe()
method, it still calls the original method and fails for the testing impediment. You cannot stub a static
method.
The only way to overcome this issue is to create a protected
method and wrap the static
call. From the code, call the wrapped method and from the test, override the protected
method.
Add a static
wrapper method in the dependency class and call the static
method from it, as shown in the following code:
public static void callMe() { throw new TestingImpedimentException("Common we dont need singleton"); } protected void wrapper() { callMe(); }
In the code, call the wrapper
method as follows:
public void testMe() { dependency.wrapper(); }
Stub the wrapper
method in the test as follows:
@Test public void testMe() throws Exception { Mockito.doNothing().when(dependency).wrapper(); aPatternLover.testMe(); }
You cannot override a final
class, so you can hide testing unfavorable features in a final
class. The following example explains the problem:
The final class hides a testing impediment as follows:
public final class FinalDepencyClass { public void poison() { throw new TestingImpedimentException("Finals cannot be mocked"); } }
The code under test has a dependency on the final class as follows:
public class FinalClassDependency { private final FinalDepencyClass finalDepencyClass; public FinalClassDependency(FinalDepencyClass finalDepencyClass) { this.finalDepencyClass = finalDepencyClass; } public void testMe() { finalDepencyClass.poison(); } }
In test, we'll try to stub the poison
method as follows:
@RunWith(MockitoJUnitRunner.class) public class FinalClassDependencyTest { @Mock FinalDepencyClass finalDependency; FinalClassDependency test; @Before public void setUp() { test = new FinalClassDependency(finalDependency); } @Test public void testMe() throws Exception { Mockito.doNothing().when(finalDependency).poison(); test.testMe(); } }
The test fails with a MockitoException as Mockito cannot mock a final class. The following is the JUnit output:
Final classes are important for framework or architecture design so that no one can hack the behavior, but it can create a serious problem for unit testing. Consider it before you choose to make a class final.
Java instantiates classes using the new
operator, but a new
operator can create problems for unit testing.
The following example explains the issue. The PoisonIvy
constructor has a testing impediment such as calls fetch data from a database table or reads from a filesystem; we represented the testing impediment with the TestingImpedimentException
:
public class PoisonIvy { public PoisonIvy() { throw new TestingImpedimentException( "Do not instantiate concrete class, use interfaces"); } public void poison() { } }
The following is the code that calls the PoisonIvy
constructor:
public class NewExpressionDependency { public void testMe() { PoisonIvy ivy = new PoisonIvy(); ivy.poison(); } }
When we unit test the testMe()
code, it fails. The testMe()
method directly creates an instance of dependency and calls the poison()
method. You cannot override this new
expression. If we want to unit test the testMe()
method, first we need to move the new
operator outside of testMe()
as we cannot instantiate the PoisonIvy
class. The constructor of PoisonIvy
throws an exception. Hence, we cannot unit test the testMe
behavior unless we move the object creation out of testMe
. Instead of creating a new instance of PoisonIvy
inside testMe()
, we can pass an instance of PoisonIvy
as a method argument, or create a class-level dependency and pass PoisonIvy
as the constructor or setter dependency argument.
What is "program to an interface, not to an implementation"?
This means program to a supertype rather than a subtype. You can interchange the implementation at runtime. In the collection framework, we have the List
interface and its many implantations. In your class, always define a variable of the List
type and not ArrayList
; at runtime, you can assign any implementation you want.
In this example, you can pass PoisonIvy
as a constructor or setter dependency, and at runtime (during testing), you can pass a mock or a fake implementation to suppress the testing impediments.
Static initializations and static
blocks are executed during class loading. You cannot override them. If you initialize a testing impediment in a static
block, then you cannot unit test the class. The following example explains the issue:
The
StaticBlockOwner
class has a static variable named StaticBlockDependency
, and it initializes the variable in a static
block. The following is the class:
public class StaticBlockOwner { private static StaticBlockDependency blockDependency; static { blockDependency = new StaticBlockDependency(); blockDependency.loadTime = new Date(); } public void testMe() { } }
When we unit test the class, it fails. The following is the unit test:
public class StaticBlockOwnerTest { StaticBlockOwner owner; @Before public void setUp() { owner = new StaticBlockOwner(); } @Test public void clean() throws Exception { owner.testMe(); } }
The test fails with a java.lang.ExceptionInInitializationError
, as it tries to instantiate the dependency in a static
block and the dependency throws an exception.
The book Working Effectively with Legacy Code, Pearson Education, by Michael Feathers explains the legacy code and how effectively you can refactor the legacy code. You can read the e-book at http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052.