Mockito Cookbook
上QQ阅读APP看书,第一时间看更新

Creating mocks of final classes with PowerMock

Although these situations should not take place in a well-written and test-driven system, there are cases in which it is necessary to mock some legacy code or third-party libraries that are impossible to be mocked only by means of Mockito. In this recipe, we will see how to deal with those abnormal situations using the PowerMock library. Remember, however, that this tool is extremely powerful and the very need to use it suggests that something may really be wrong with your code. The best outcome of using this library would be to use it as means to refactor the bad code and, at the end of the day, remove the PowerMock dependency from the system since it is no longer needed.

Getting ready

In order to use PowerMock with Mockito, you need to include the following library in your classpath. If you are using a dependency management system such as Gradle or Maven, you can add it to the code as follows:

The dependency definition for Gradle is as follows:

testCompile 'org.powermock:powermock-api-mockito:1.5.2'

The dependency definition for Maven is as follows:

<dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-api-mockito</artifactId>
      <version>1.5.2</version>
      <scope>test</scope>
</dependency>      

Now, depending on the integration with JUnit or TestNG, there is an additional JAR file needed.

If you are using JUnit, then provide either of the following dependencies:

The dependency definition for Gradle is as follows:

testCompile 'org.powermock:powermock-module-junit4:1.5.2'

The dependency definition for Maven is as follows:

<dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-junit4</artifactId>
      <version>1.5.2</version>
      <scope>test</scope>
</dependency>      

Assuming that you are using TestNG, configure your dependencies as follows:

The dependency definition for Gradle is as follows:

testCompile 'org.powermock:powermock-module-testng:1.5.2'

The dependency definition for Maven is as follows:

<dependency>
      <groupId>org.powermock</groupId>
      <artifactId>powermock-module-testng</artifactId>
      <version>1.5.2</version>
      <scope>test</scope>
</dependency>      

You can also download the JAR files from PowerMock's website at https://code.google.com/p/powermock/wiki/Downloads?tm=2.

Our system under test will be a class whose responsibility it is to calculate the tax factor for the current person. It interacts with TaxService, which happens to be a final class (we'll omit its implementation since it's irrelevant for this recipe; what's important to remember is that it's a final class). Have a look at the following code:

public class TaxFactorCalculator {

    public static final double INVALID_TAX_FACTOR = -1;

    private final TaxService taxService;

    public TaxFactorCalculator(TaxService taxService) {
        this.taxService = taxService;
    }

    public double calculateTaxFactorFor(Person person) {
        try {
            return taxService.calculateTaxFactorFor(person);
        } catch (Exception e) {
            System.err.printf("Exception [%s] occurred while trying to calculate tax for person [%s]%n", e, person.getName());
            return INVALID_TAX_FACTOR;
        }
    }

}

How to do it...

To use PowerMock with JUnit, you have to perform the following steps:

  1. Annotate your test class with @RunWith(PowerMockRunner.class).
  2. Provide all the classes that need to be prepared for testing (most likely bytecode manipulated) in the @PrepareForTest annotation (in the case of our scenario, it would be @PrepareForTest(TaxService.class) since TaxService is a final class). In general, classes that need to be prepared for testing will include classes with final, private, static or native methods; classes that are final and that should be mocked; and also classes that should be returned as mocks on instantiation.

Let's take a look at the JUnit test that will verify whether the tax factor is properly calculated (I'm using the BDDMockito.given(...) and AssertJ's BDDAssertions.then(...) static methods. Check out Chapter 7, Verifying Behavior with Object Matchers, on how to work with AssertJ or how to do the same with Hamcrest's assertThat(...)). Have a look at the following code:

@RunWith(PowerMockRunner.class)
@PrepareForTest(TaxService.class)
public class TaxFactorCalculatorTest {

    static final double TAX_FACTOR = 10000;

    @Mock TaxService taxService;

    @InjectMocks TaxFactorCalculator systemUnderTest;

    @Test
    public void should_calculate_tax_factor() {
        // given
        given(taxService.calculateTaxFactorFor(Mockito.any(Person.class))).willReturn(TAX_FACTOR);

        // when
        double taxFactorForPerson = systemUnderTest.calculateTaxFactorFor(new Person());

        // then
        then(taxFactorForPerson).isEqualTo(TAX_FACTOR);
    }
    
}

To use PowerMock with TestNG, you have to perform the following steps:

  1. Make your class extend the PowerMockTestCase class.
  2. Implement a method annotated with the @ObjectFactory annotation that returns an instance of the PowerMockObjectFactory class (this object factory will be used for the creation of all object instances in the test).
  3. Provide all the classes that need to be prepared for testing (most likely bytecode manipulated) in the @PrepareForTest annotation (in the case of our scenario, it would be @PrepareForTest(TaxService.class) since TaxService is a final class). This includes classes with final, private, static, or native methods; classes that are final and that should be mocked; and also classes that should be returned a mock on object instantiation.

Let's take a look at the following JUnit test that will verify whether the tax factor is properly calculated (consult the introduction to the analogous JUnit example discussed earlier in terms of BDDMockito and BDDAssertions usage):

@PrepareForTest(TaxService.class)
public class TaxFactorCalculatorTestNgTest extends PowerMockTestCase {

    static final double TAX_FACTOR = 10000;

    @Mock TaxService taxService;

    @InjectMocks TaxFactorCalculator systemUnderTest;

    @Test
    public void should_calculate_tax_factor() {
        // given
        given(taxService.calculateTaxFactorFor(any(Person.class))).willReturn(TAX_FACTOR);

        // when
        double taxFactorForPerson = systemUnderTest.calculateTaxFactorFor(new Person());

        // then
        then(taxFactorForPerson).isEqualTo(TAX_FACTOR);
    }

    @ObjectFactory
    public IObjectFactory getObjectFactory() {
        return new PowerMockObjectFactory();
    }
    
}

How it works...

The internals of PowerMock go far beyond the scope of this recipe but the overall concept is that part of the logic of PowerMockRunner is to create a custom classloader and bytecode manipulation for the classes defined using the @PrepareForTest annotation in order to mock them and to use these mocks with the standard Mockito API. Due to bytecode manipulations, PowerMock can ignore a series of constraints of the Java language, such as extending final classes.

See also