PowerMockito -- Mock and Spy

3 minutes read

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();

Comments