Monday, 17 October 2011

Using PowerMock to Mock Constructors

In my opinion, one of the main benefits of dependency injection is that you can inject mock and/or stub objects into your code in order to improve testability, increase test coverage and write better and more meaningful tests. There are those times, however, when you come across some legacy code that doesn’t use dependency injection and held together by composition rather than aggregation.

When this happens, you have three options:
  1. Ignore the problem and not write any tests.
  2. Refactor like mad, changing everything to use dependency injection.
  3. Use PowerMock to mock constructors

Obviously, option 1 isn’t a serious option, and although I’d recommend refactoring to move everything over to dependency injection, that takes time and you have to be pragmatic. That’s where PowerMock comes in... this blog demonstrates how to use PowerMock to mock a constructor, which means that when your code calls new it doesn’t create a real object, it creates a mock object.

To demonstrate this idea, the first thing we need is some classes to test, which are shown below.

public class AnyOldClass {

 
public String someMethod() {
   
return "someMethod";
 
}
}

public class UsesNewToInstantiateClass {

 
public String createThing() {

   
AnyOldClass myclass = new AnyOldClass();

    String returnValue = myclass.someMethod
();
   
return returnValue;
 
}
}

The first class, AnyOldClass, is the class that the code instantiates by calling new. In this example, as the name suggests, it can be anything.

The second class, the aptly named UsesNewToInstantiateClass, has one method, createThing(), which when called does a:

    AnyOldClass myclass = new AnyOldClass();

This is all pretty straight forward, so we’ll move quickly on to the PowerMock assisted JUnit test:

import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.powermock.api.easymock.PowerMock.expectNew;
import static org.powermock.api.easymock.PowerMock.replay;
import static org.powermock.api.easymock.PowerMock.verify;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.annotation.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(UsesNewToInstantiateClass.class)
public class MockConstructorTest {

 
@Mock
 
private AnyOldClass anyClass;

 
private UsesNewToInstantiateClass instance;

 
@Test
 
public final void testMockConstructor() throws Exception {

   
instance = new UsesNewToInstantiateClass();

    expectNew
(AnyOldClass.class).andReturn(anyClass);

   
final String expected = "MY_OTHER_RESULT";
    expect
(anyClass.someMethod()).andReturn(expected);

    replay
(AnyOldClass.class, anyClass);
    String result = instance.createThing
();
    verify
(AnyOldClass.class, anyClass);
    assertEquals
(expected, result);
 
}

}

Firstly, this class has the usual PowerMock additions of:

@RunWith(PowerMockRunner.class)
@PrepareForTest(UsesNewToInstantiateClass.class)

at the top of the file plus the creation of the anyOldClass mock object. The important line of code to consider is:

    expectNew(AnyOldClass.class).andReturn(anyClass);

This line of code tells PowerMock to expect a call to new AnyOldClass() and return our anyClass mock object.

Also of interest are the calls to replay and verify. In the example above, they both have two arguments. The first, AnyOldClass.class relates to the expectNew(...) call above, whilst the second, anyClass refers to the straight forward mock call expect(anyClass.someMethod()).andReturn(expected);.

There are those times when you should really let new do what it does: create a new object of the requested type. There is a body of opinion that says you can over-isolate your code when testing and that mocking everything reduces the meaning and value of a test. To me there’s no right answer to this and it’s a matter of choice. It’s fairly obvious that if your code accesses an external resource such as a database, then you’d either refactor and implement DI or use PowerMock. If your code under test doesn’t access any external resources, then it’s more of a judgement call on how much code isolation is too much? This perhaps needs some thought and may be the subject for another blog on anther day...

1 comment:

rliesenfeld said...

Option 4: use JMockit.

import org.junit.*;
import mockit.*;

public class MockConstructorTest
{
@Tested
UsesNewToInstantiateClass instance;

@Test
public void mockConstructor()
{
final String expected = "MY_OTHER_RESULT";

new Expectations() {
AnyOldClass anyClass;

{
anyClass.someMethod();
result = expected;
}
};

String result = instance.createThing();

assertEquals(expected, result);
}
}