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).
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"); } }
To use PowerMock with JUnit to stub a final method, you have to perform the following steps:
@RunWith(PowerMockRunner.class)
.@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.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:
PowerMockTestCase
class.@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).@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.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(); } }
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.
3.144.35.122