PowerMockito -- Mock and Spy
A bad system design can lead to much hard work. In order to increase the unit tests coverage, I recently started to work on writing unit tests for some classes. One of the case is I want to test a method as follow:
public final ReturnType getMethod (SomeRequest someRequest) {
AnotherRequest anotherRequest = new AnotherRequest(someRequest);
SomeResponse someResponse = SomeService.getInstance().someMethod(anotherRequest);
SomeValue someValue = someResponse.getValue();
/**
* Some processes with someValue..
*/
}
The main purpose of this test is testing the process with someValue
, so I should just mock the .getValue()
method. But the thing is not that easy. Let me put more related classes here:
SomeService.class
public final class SomeService {
private static SomeService instance = new SomeService();
static {
// A static block
}
protected SomeService() {}
public static SomeService getInstance() {
return instance;
}
public SomeResponse someMethod(AnotherRequest anotherRequest) {
// Implementation of the method..
}
}
SomeResponse.class
public class SomeResponse {
public SomeValue getValue() {
// Implementation of getValue()
}
}
SomeValue.class
public class SomeValue {
private String name;
private void populateValue(PreDefinedType preDefinedType) {
// Generate name from a preDefinedType, basically a black box.
}
public String getName() {
return name;
}
}
If I put the unit test as follow, I will get the java.lang.reflect.InvocationTargetException
Error.
@RunWith(PowerMockRunner.class)
@PrepareForTest({SomeService.class, SomeResponse.class, SomeValue.class, SomeRequest.class})
public class ElasticSearchBasedTokenGeneratorServiceTest extends PowerMockTestCase {
// Mock every class and call..
// Find out more on https://github.com/jayway/powermock/wiki/MockitoUsage
}
It seems that mock library does not always work. A better way to do it is to create a Utilities.class
to reset final
static
fields.
public class Utilities {
public static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
// remove final modifier from field
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
}
And then in the unit test, do the following:
@RunWith(PowerMockRunner.class)
@PrepareForTest({SomeService.class, SomeResponse.class, SomeValue.class, SomeRequest.class})
public class SomeTest extends PowerMockTestCase {
public void testMethod() throws Exception {
// Mock the request
SomeRequest someRequest = PowerMockito.mock(SomeRequest.class);
Mockito.when(entitiesRequest.method()).thenReturn("some thing");
// Mock response and its related value
SomeResponse someResponse = PowerMockito.mock(SomeResponse.class);
SomeValue someValue = PowerMockito.mock(SomeValue.class);
Mockito.when(someValue.getName()).thenReturn("some name");
Mockito.when(someResponse.getValue()).thenReturn(someValue);
// Mock the service
SomeService someService = PowerMockito.mock(SomeService.class);
Mockito.when(someService.someMethod(Mockito.any())).thenReturn(someResponse);
// To initialize the service, use the reflect method created above
Utilities.setFinalStatic(SomeService.class.getDeclaredField("someService"), someService);
SomeService someService = new SomeService();
someService.someMethod(someRequest);
// Assertion..
}
}
One thing I should mention is, in the actual SomeService.class
, we should change the initialization of service
outside the method (as a field):
private static final SomeService someService = SomeService.getInstance();