In some badly written code, you can find cases in which the system under test's collaborators are not passed into the object in any way (for example, by the constructor), but the object itself instantiates them via the new
operator. The best practice would be to not write like this in the first place. But let's assume that you have inherited such a code and, since we follow the boy scout rule, that you should leave the code that you've encountered in a better state than you the one in which you have found it in the first place. We have to do something about this.
The very step of the refactoring of such a scenario is presented in Chapter 8, Refactoring with Mockito. This is why, in the current recipe, we will just learn how to stub object initialization in such a way that instead of creating a new instance of an object, a mock will be returned. Unfortunately, Mockito can't perform such stubbing, and that's why we will use PowerMock to do that.
Even though you might have already seen this warning, I'd like to yet again remind you that it absolutely isn't a good practice to use PowerMock in your well-written code. If you follow all of the SOLID principles (please refer to Chapter 2, Creating Mocks, for the explanation of each of those principles), then you should not resort to stubbing static methods. PowerMock can come in handy when dealing with the legacy code or stubbing third-party libraries (you can check Chapter 8, Refactoring with Mockito, to see how to use PowerMock to refactor the badly written code).
To use PowerMock, you have to add it to your classpath. Please check the Creating mocks of final classes with PowerMock recipe in Chapter 2, Creating Mocks, for more details on how to add PowerMock to your project.
For this recipe, our system under test will be MeanTaxFactorCalculator
, which calls a TaxFactorFetcher
object's static methods twice to get a tax factor for the given person and then calculates a mean value for those two results as follows:
public class MeanTaxFactorCalculator { public double calculateMeanTaxFactorFor(Person person) { TaxFactorFetcher taxFactorFetcher = new TaxFactorFetcher(); double taxFactor = taxFactorFetcher.getTaxFactorFor(person); double anotherTaxFactor = taxFactorFetcher.getTaxFactorFor(person); return (taxFactor + anotherTaxFactor) / 2; } }
Let's assume that TaxFactorFetcher
is a class that calculates a person's tax factor in a different way depending on his or her origin.
To use PowerMock with JUnit to stub object instantiation, you have to perform the following steps:
@RunWith(PowerMockRunner.class)
.@PrepareForTest
annotation (in the case of our scenario, this would be @PrepareForTest(MeanTaxFactorCalculator.class)
since that class needs to be manipulated in order to stub the execution of the TaxFactorFetcher
constructor).PowerMockito.whenNew(ClassToStub.class)
method together with additional stubbing configuration (whether the constructor has no arguments or has precisely provided parameters, and so on).Let's take a look at the JUnit test which will verify whether the tax factor is properly calculated (remember that I'm using BDDMockito.given(...)
and AssertJ's BDDAssertions.then(...)
static methods. Check out Chapter 7, Verifying Behavior with Object Matchers, for more details on how to work with AssertJ or how to do the same with Hamcrest's assertThat(...)
):
@RunWith(PowerMockRunner.class) @PrepareForTest(MeanTaxFactorCalculator.class) public class MeanTaxFactorCalculatorTest { @Mock TaxFactorFetcher taxFactorFetcher; MeanTaxFactorCalculator systemUnderTest = new MeanTaxFactorCalculator(); @Test public void should_calculate_tax_factor_for_a_player_from_undefined_country() throws Exception { // given double expectedMeanTaxFactor = 10; whenNew(TaxFactorFetcher.class).withNoArguments().thenReturn(taxFactorFetcher); given(taxFactorFetcher.getTaxFactorFor(any(Person.class))).willReturn(5.5, 14.5); // when double meanTaxFactor = systemUnderTest.calculateMeanTaxFactorFor(new Person()); // then then(meanTaxFactor).isEqualTo(expectedMeanTaxFactor); } }
To use PowerMock with TestNG to create a spy for final classes, you have to perform the following steps:
PowerMockTestCase
class.@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).@PrepareForTest
annotation (in the case of our scenario, this would be @PrepareForTest(MeanTaxFactorCalculator.class)
since that class needs to be manipulated in order to stub the execution of the TaxFactorFetcher
constructor).PowerMockito.whenNew(ClassToStub.class)
method together with the additional stubbing configuration (whether the constructor has no arguments or has precisely provided parameters, and so on).Let's take a look at the TestNG test which will verify whether the tax factor is properly calculated (refer to the introduction to the analogous JUnit example discussed earlier in terms of the BDDMockito
and BDDAssertions
usage):
@PrepareForTest(MeanTaxFactorCalculator.class) public class MeanTaxFactorCalculatorTestNgTest extends PowerMockTestCase { @Mock TaxFactorFetcher taxFactorFetcher; MeanTaxFactorCalculator systemUnderTest = new MeanTaxFactorCalculator(); @Test public void should_calculate_tax_factor_for_a_player_from_undefined_country() throws Exception { // given double expectedMeanTaxFactor = 10; whenNew(TaxFactorFetcher.class).withNoArguments().thenReturn(taxFactorFetcher); given(taxFactorFetcher.getTaxFactorFor(any(Person.class))).willReturn(5.5, 14.5); // when double meanTaxFactor = systemUnderTest.calculateMeanTaxFactorFor(new Person()); // then then(meanTaxFactor).isEqualTo(expectedMeanTaxFactor); } @ObjectFactory public IObjectFactory getObjectFactory() { return new PowerMockObjectFactory(); } }
Please remember that you should resort to stubbing object instantiation only if you absolutely know what you are doing: you are familiar with the SOLID principles and you are going to follow them. There are cases (dealing with the legacy code or third-party dependencies) that you would like to mock where using PowerMock can be handy.
Use PowerMock to write the tests for the bad code and then refactor it so that you no longer need to have PowerMock on the classpath.
The internals of PowerMock go far beyond the scope of this recipe, but the overall concept is such 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 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.146.176.145