Stubbing final methods with PowerMock

In this recipe, we will stub a final method and verify the object under test's behavior using JUnit. Since Mockito can't stub final methods, we'll use PowerMock to do it.

As usual, when dealing with PowerMock, you have to be really sure of what you are doing. You shouldn't need to use it with well-written code. Just follow the SOLID principles (see Chapter 2, Creating Mocks, for more information) and you shouldn't have the need to use this library.

PowerMock can be useful when dealing with legacy code or stubbing third-party libraries (you can check Chapter 8, Refactoring with Mockito, to see how to use PowerMock to refactor legacy code).

Getting ready

To use PowerMock, you have to add it to your classpath. Check the Creating mocks of final classes with PowerMock recipe in Chapter 2, Creating Mocks, for details on how to add PowerMock to your project.

As shown in the following code, for this recipe, our system under test will be a unit of a PersonProcessor class and its collaborator, the PersonSaver class. The latter is responsible for logging warnings while validating the person and for persisting the person in the database:

public class PersonProcessor {

    private final PersonSaver personSaver;

    public PersonProcessor(PersonSaver personSaver) {
        this.personSaver = personSaver;
    }

    public boolean process(Person person) {
        try {
          personSaver.validatePerson(person);
            personSaver.savePerson(person);
            return true;
        } catch (Exception exception) {
            System.err.printf("Exception occurred while trying save person [%s]%n", exception);
            return false;
        }
    }

}

Now, let's take a look at the PersonSaver class that has a single final method that we want to stub:

public class PersonSaver {

    public void validatePerson(Person person) {
        if (!person.isCountryDefined()) {
          System.out.printf("Warning person [%s] has undefined country%n", person.getName());
        }
    }

    public final void savePerson(Person person) {
      // simulating web service call
      System.out.println("Storing person in the db");
    }

}

How to do it...

To use PowerMock with JUnit to stub a final method, 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 byte-code manipulated) in the @PrepareForTest annotation (in the case of our scenario, it would be @PrepareForTest(PersonSaver.class) since PersonSaver has a final method that we want to stub). In general, the class that needs to be prepared for testing will include classes with final, private, static, or native methods; classes that are final and that should be spied on; and also classes that should be returned as spies on instantiation.
  3. Since we are creating a PowerMock spy, we can't profit from Mockito's annotations and need to create a spy using the PowerMockito.spy(…) method.

The following snippet depicts the aforementioned scenario for JUnit. See Chapter 1, Getting Started with Mockito, for the TestNG configuration (I'm using the BDDMockito.given(...) and AssertJ's BDDAssertions.then(...) static methods. Check out Chapter 7, Verifying Behavior with Object Matchers, for details 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(PersonSaver.class)
public class PersonProcessorTest {

    PersonSaver personSaver = PowerMockito.spy(new PersonSaver());

    PersonProcessor systemUnderTest = new PersonProcessor(personSaver);

    @Test
    public void should_successfully_proces_person_with_defined_country() {
        // given       
        willDoNothing().given(personSaver).savePerson(any(Person.class));

        // when
        boolean result = systemUnderTest.process(new Person("POLAND"));

        // then
        then(result).isTrue();
    }
  
}

Now, let's see how to configure our class to work with TestNG:

  1. Make your class extend the PowerMockTestCase class.
  2. Implement a method annotated with @ObjectFactory 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(PersonSaver.class) since PersonSaver has a final method that we want to stub). In general, the class that needs to be prepared for testing would include classes with final, private, static or native methods; classes that are final and that should be spied on; and also classes that should be returned as spies on instantiation.
  4. Since we are creating a PowerMock spy, we can't profit from Mockito's annotations and need to create a spy using the PowerMockito.spy(…) method.

Let's check the following TestNG test (see the JUnit example for the warnings in terms of static imports and the BDD approach):

@PrepareForTest(PersonSaver.class)
public class PersonProcessorTestNgTest extends PowerMockTestCase {

    PersonSaver personSaver;
  
    PersonProcessor systemUnderTest;
  
  @BeforeMethod
  public void setup() {
    personSaver = PowerMockito.spy(new PersonSaver());
    systemUnderTest = new PersonProcessor(personSaver);
  }

    @Test
    public void should_successfully_proces_person_with_defined_country() {
        // given       
        willDoNothing().given(personSaver).savePerson(any(Person.class));

        // when
        boolean result = systemUnderTest.process(new Person("POLAND"));

        // then
        then(result).isTrue();
    }


  @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 a part of the PowerMockRunner logic 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

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

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