Designing for testability with Mockito

We learned about testing impediments and how to refactor them. We cannot unit test code when testing impediments are present; we refactor code and move the impediments out (to another class or method), and during testing, the impediments are replaced with mock objects. PowerMock is a dirty solution and it should only be used for legacy code. This is the better way is to refactor the source and make more test friendly.

However, sometimes we cannot mock out the external dependencies due to testing-unfriendly design. This section covers the design for testability, or rather, things to avoid in code. The following Java constructs go up against mocking the testing impediments:

  • Constructors initialize testing impediments
  • Class level variable declaration and initialization
  • Private methods
  • Final methods
  • Static methods
  • Final classes
  • Use of new
  • Static variable declaration and initialization
  • Static initialization blocks

You cannot unit test legacy code because it is either tightly coupled, or testing unfavorable language constructs hide the testing impediments. The following section explains testing unfavorable constructs.

Note

To represent a testing impediment, we'll throw a special runtime exception TestingImpedimentException. If your test fails with a TestingImpedimentException error, it means you cannot automate the test, as your code has testing unfavorable features.

Identifying constructor issues

To build a test, we need to instantiate the class in test harness, but the problem with legacy code is that it is difficult to break dependency and instantiate a class in a test harness. For example, in a constructor, the class instantiates many objects, reads from the properties file, or even creates a database connection. There could 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 the 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 testing impediments. The TestingUnfavorableConstructor class creates dependencies in the constructor. The dependencies represent the database access and the file reads from the TestingUnfavorableConstructor constructor. The following is the 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, we need to create an object for the TestingUnfavorableConstructor class. However, when we try to create an instant in the unit test, the class fails to indicate that the class cannot be instantiated from an automated test suite. The following is the output:

Identifying constructor issues

To overcome this, you should inject the dependencies through the constructor instead of creating them in the constructor.

We cannot modify the default constructor because the class is invoked from many other clients; we cannot break the clients. Two other options are as follows:

  • Keep the default constructor as it is, create another constructor, and inject dependencies through this new constructor. From test, we can call this new constructor. You're also putting the code into production for testing purpose; this is a test smell, but to overcome this, you need to unit test the code with PowerMock.
  • Create a protected method, move the dependency instantiation to that method, create two setter methods, and initialize the dependencies through a setter injection. In the test, create a fake object for the main class, override the protected method to do nothing, and pass the dependencies through setter methods.

The first option is relatively straightforward; 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 class, 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 {
  }
}

The empty test method is used to check the health of the test setup, as you need at least one test method to invoke the setup method.

Tip

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 setter method of the code under the test.

Realizing initialization issues

Declaring the class-level variable and instantiating the object at the same time creates a problem; you don't get a 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:

Public class VariableInitialization {
  DatabaseDependency dependency1 = new DatabaseDependency();
  public void testMe(Object obj) {
    
  }
}

When you instantiate the VariableInitialization class in the test, the test fails. The following screenshot shows the output:

Realizing initialization issues

Here is the test class:

public class VariableInitializationTest {
  VariableInitialization initialization;

  @Before public void setUp() throws Exception {
    initialization = new VariableInitialization();
  }
  @Test public void sanity() throws Exception {
  }
}

The following are the options to overcome class-level variable initialization:

  • Add a default constructor and move the dependency instantiation to the default constructor, create another constructor, and inject dependencies through this new constructor. From the test, we can call this new constructor. This is a test smell, as the code is added in production for testing purposes.
  • Add a default constructor, move the dependency instantiation to a protected method, call the method from the default constructor, create a setter method, and initialize the dependency through a setter injection. In the test, create a fake object of the main class and override the protected method to do nothing, and pass the dependencies through the setter methods.

    Tip

    Do not instantiate the testing impediment variables at the class level. You can still instantiate variables, such as list = new ArrayList<String>() and more, which are totally reasonable to build internal fields in themselves; it's the difference between coupling to collaborating classes and the internal state.

Working with private methods

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 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:

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:

Working with private methods

You can extract the testing impediments to a protected (or default package visibility) 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. Objects should do one thing; if you've got a method you want to test that does X and is in the same class as a method that does Y (which is so totally different that it can't be allowed to happen in your test for X), then your class must be doing two things. Split these responsibilities.

In this example, validating objects and showing errors are two different responsibilities and should be managed by two different classes.

Tip

Do not hide testing impediments in private methods.

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 overrides the protected method with an empty implementation. The test code invokes the validate() method on the new anonymous implementation of the PrivateMethodRefactored class, and in turn, the polymorphic behavior calls 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);
  }
}

Tip

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 the test contains many testing impediments, it is not possible to override all of them in an anonymous class; rather, we can create an inner class, extend the code under test, and override all testing-unfriendly methods.

Working with final 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 demonstrates the issue.

The FinalDependency class has a final method called 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:

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:

@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, it 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:

Working with final methods

Tip

Do not hide the testing impediments in the final methods; you cannot override or stub a final method.

A possible way to overcome this is by extracting the content of the final method to a protected method, calling the protected method from the final method, and overriding the protected method in the test. If you cannot touch the class at all, use the PowerMock framework. For example, when you have only a JAR file, create a MethodDependency interface with FinalDependency, implementing it as we have done here, rather than having FindalMethodDependency depend on MethodDependency. Then in production, you need to provide a FinalMethodDependency instance (as done here), but in tests, you can stub the interface happily, which doesn't have any final methods, and you are all set to proceed.

Exploring static method issues

Static methods are good for utility classes but unnecessary use of static can hide the testing impediments and create a problem for unit testing. The following example demonstrates 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 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.

Tip

Do not hide the testing impediments in static methods; you cannot stub static methods.

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. We will now add a wrapper method in the dependency class and call the static method from it:

  public static void callMe() {
    throw new TestingImpedimentException("Come on we dont need singleton");
  }

  protected void wrapper() {
   callMe();
  }
}

From the code, call the wrapper method:

  public void testMe() {
   dependency.wrapper();
  }

Stub the wrapper method in the test:

@Test
  public void testMe() throws Exception {
    Mockito.doNothing().when(dependency).wrapper();
    aPatternLover.testMe();
  }

The better way to do this is to stop calling the static method from this class entirely and wrap it in a separate class, which you pass in as a dependency.

Say you've got a Database.create() static method you call from your class A. You could have a DatabaseBuilder class which you pass into class A, and then just have a call databaseBuilder.create(), where DatabaseBuilder is something like the following:

public class DatabaseBuilder {
  public void create() {
    Database.create();
  }
}

And then in the tests, you just provide a stubbed database builder and swap out the whole thing. I would really not recommend using this pattern of making private methods protected and overriding them, except where it's absolutely necessary.

Alternatively, of course, if you can't change the API; you'd use PowerMock to stub the static call.

Working with final classes

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:

public final class FinalDepencyClass {

  public void poison() {
    throw new TestingImpedimentException("Finals cannot be mocked");
  }
}

The code under the test has a dependency on the final class:

public class FinalClassDependency {
  private final FinalDepencyClass finalDepencyClass;

  public FinalClassDependency(FinalDepencyClass finalDepencyClass) {
    this.finalDepencyClass = finalDepencyClass;
  }

  public void testMe() {
    finalDepencyClass.poison();
  }
}

In the test, we'll try to stub the poison method:

@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 error as Mockito cannot mock a final class. The following screenshot displays the JUnit output:

Working with final classes

Tip

Do not hide the testing impediments in final classes; you cannot mock a final class.

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. Before you choose to make a class final, ensure that your final class is a final implementation of some interface so that other clients of the class can potentially use stubbed instances of the interface.

Learning new concerns

Java instantiates classes using the new operator, but an innocent new can create a problem for unit testing.

The following example explains the issue. The PoisonIvy constructor has a testing impediment, for instance, when the method call fetches data from a database table or reads from the filesystem, we represent the testing impediment with the TestingImpedimentException error:

public class PoisonIvy {

  public PoisonIvy() {
    throw new TestingImpedimentException("use dependency injection");
  }

  public void poison() {

  }
}

The following is the code that calls the PoisonIvy class:

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 testMe(), as we cannot instantiate the PoisonIvy class. The constructor of the PoisonIvy class 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 a constructor- or setter-dependency argument.

Tip

Program to an interface, not an implementation. Rather than hardcoding the collaborator instantiation of the subtype into the code, assign the concrete collaborator implementation object through a dependency injection. Separate the parts of the codebase that use objects, and implement your logic from the parts that decide which objects to use where (typically with a DI framework such as Guice or Spring).

What does program to an interface, not an implementation mean? It means, program to a super type, rather than a subtype. You can interchange the implementation at runtime. In a collection framework, we have the List interface and its many implementations. In your class, always define a variable or method return type for List not ArrayList, so that if required, you can assign any implementation you want.

In this example, you can pass the PoisonIvy class 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.

Exploring static variables and blocks

Static initializations and static blocks are executed during class loading; you cannot override them. If you initialize a testing impediment in a static block, you cannot unit test the class. The following example demonstrates the issue.

The StaticBlockOwner class has a static variable known as 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 java.lang.ExceptionInInitializationError as it tries to instantiate the dependency in a static block and the dependency throws an exception.

Tip

Do not instantiate dependencies in a static block. You cannot override the testing impediments; you shouldn't be using static initializer blocks at all.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.18.101