Creating spies of final classes with PowerMock

Before going into the details of this recipe, if you haven't already done so, please read the Creating mocks of final classes with PowerMock recipe of Chapter 2, Creating Mocks. PowerMock is a powerful (thus dangerous) tool, that in the hands of an inexperienced developer, can lead to the creation of really bad test and production code.

Why would you want to use PowerMock? Mockito can't create mocks for classes that are final. The same problem exists when trying to create spies. If you have a properly written test-driven code, you shouldn't have the need to use either spies or partial mocks, nor have PowerMock in your project. If you need to use PowerMock to create spies of final classes, do it only as a last resort in order to refactor your code, and at the end remove PowerMock dependencies and only use Mockito. Refer to Chapter 8, Refactoring with Mockito, for examples of how to use PowerMock as a mean to refactor your code (and at the end of the day, remove PowerMock from your classpath).

Getting ready

Speaking of classpaths, in order to use PowerMock with Mockito, you need to add it to your classpath. Please refer to the Creating Mocks of final classes with PowerMock recipe of Chapter 2, Creating Mocks, to know how to add PowerMock to your project.

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

public class TaxFactorProcessor {

    public static final double INVALID_TAX_FACTOR = -1;

    private final TaxService taxService;

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

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

}

How to do it...

To use PowerMock with JUnit to create a spy for final classes, you need to perform the following steps:

  1. Annotate your test class with @RunWith(PowerMockRunner.class).
  2. Provide all of the classes that need to be prepared for testing (most likely byte-code 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, the classes that need to be prepared for testing would include those with final, private, static, or native methods. These are classes that are final and should be spied on, and are also classes that should be returned as spies on instantiation.
  3. Annotate the field to be spied with the @Spy annotation and instantiate that object (it differs from the standard Mockito approach, where if the spy has a default constructor, you wouldn't need to instantiate it).

Let's take a look at the JUnit test which will verify whether the tax factor is properly calculated (remember that I'm using the BDDMockito.given(...) and AssertJ's BDDAssertions.then(...) static methods. Refer to Chapter 7, Verifying Behavior with Object Matchers, to know 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 TaxFactorProcessorTest {

    static final double TAX_FACTOR = 10000;

    @Spy TaxService taxService = new TaxService();

    @InjectMocks TaxFactorProcessor systemUnderTest;

    @Test
    public void should_return_default_tax_factor_for_person_from_undefined_country() {
        // given
        doReturn(TAX_FACTOR).when(taxService).calculateTaxFactorFor(Mockito.any(Person.class));

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

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

}

To use PowerMock with TestNG to create a spy for final classes, you need 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 of the classes that need to be prepared for testing (most likely byte-code 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. These are classes that are final and that should be spied on; and are also classes that should return a spy on object instantiation.
  4. Annotate the field to be spied with the @Spy annotation and instantiate that object (it differs from the standard Mockito approach, where if the spy has a default constructor, you wouldn't need to instantiate it).

Let's take a look at the TestNG test that will verify whether the tax factor is properly calculated (refer to the introduction to the preceding analogous JUnit example for more information on BDDMockito and BDDAssertions usage):

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

    static final double TAX_FACTOR = 10000;

    @Spy TaxService taxService = new TaxService();

    @InjectMocks TaxFactorProcessor systemUnderTest;

    @Test
    public void should_return_default_tax_factor_for_person_from_undefined_country() {
        // given
        doReturn(TAX_FACTOR).when(taxService).calculateTaxFactorFor(Mockito.any(Person.class));

        // when
        double taxFactorForPerson = systemUnderTest.processTaxFactorFor(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 such that a part of the PowerMockRunner logic is to create a custom classloader and byte-code 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 byte-code manipulations, PowerMock can ignore a series of constraints of the Java language, such as extending final classes.

See also

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

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