Photo by Joshua Woroniecki on Unsplash
Limitation of PowerMockito.whenNew()
Why Powermockito.whenNew() is not enough.
Table of contents
Powemock is a very powerful tool in any Java Developer's arsenal, it allows a developer to change his code at the bytecode level. This is especially beneficial when we want to modify some functionality of source (System Under Test).
Whey we need whenNew()
?
whenNew()
allows us to inject our fake
or mock
instead of actually creating a new object of said class.
This works great, we just need to add our class in @PrepareForTest
annotation and PowerMock will inject our mock. Just like that.
Here is an example class:
// Example Source Class
public class ExampleClass {
public int getHeight() {
// Lets say we have a generic method which provides different result base on the provided id
final CustomClass customClassInstance = new CustomClass();
return customClass.getHeight();
}
}
and here is a test case using whenNew()
injection.
@RunWith(PowerMockRunner.class)
@PrepareForTest({
ExampleClass.class
})
public class ExampleClassTest {
private ExampleClass classUnderTest;
@Before
public void setUp() throws Exception {
// Create constructor of ExampleClass
classUnderTest = new ExampleClass();
}
@Test
public void getHeight_shoudReturnHeightOfView() throws NoSuchMethodException {
final int expectedHeight = 100;
// Given
final CustomClass mckCustomClass = Mockito.mock(CustomClass.class);
Mockito.when(mckCustomClass.getHeight()).theReturn(expectedHeight);
// Make sure you match exact parameters while mocking (same as we do for Mockito.mock statements).
// There is also withArguments(first, second, ....)
// and withAnyArguments() for cases when you don't care for arguments passed.
PowerMockito.whenNew(CustomClass.class).withNoArguments().thenReturn(mckCustomClass);
final int actualResult = classUnderTest.getHeight();
Assert.assertEquals(expectedHeight, actualResult);
}
}
Now let's come to the problem...
Let's say you have the following class to test:
// ClassToTest
public class ClassToTest {
// heavy method
private void doInBackground() {
new Thread() {
@Override
public void run() {
final CustomClass customClassInstance = new CustomClass();
int height = customClass.getHeight();
// ... some heavey operation on height
}
}.start();
}
}
Now, in the above example, a new instance of CustomClass
is created inside a nested scope (Thread
), hence modifying bytecode of ClassToTest
will no longer work as new CustomClass()
is not inside its scope.
What to do?
Powermock has other tools to deal with such situations, firstly, you need to add CustomClass
in @PrepareForTest()
and suppress()
constructors and stub()
getHeight()
method to modify its behaviour according to your test requirements.
Here is example code:
@RunWith(PowerMockRunner.class)
@PrepareForTest({
ExampleClass.class,
CustomClass.class
})
public class ExampleClassTest {
private ExampleClass classUnderTest;
@Before
public void setUp() throws Exception {
// Create constructor of ExampleClass
classUnderTest = new ExampleClass();
}
@Test
public void getHeight_shoudReturnHeightOfView() throws NoSuchMethodException {
final int expectedHeight = 100;
// Given
PowerMockito.suppress(CustomClass.class.getConstructors());
PowerMockito.stub(CustomClass.class.getMethod("getHeight")).toReturn(expectedHeight);
// ...
}
}
P.S. PowerMock has suppress()
, sub()
and replace()
methods to modify individual elements of any class. Please read more about methods declared in PowerMockito
class and see if you find an alternate solution to your use case.