J
J
Java Testing Notes
Ask or search…
K

Limitation of PowerMockito.whenNew()

Why Powermockito.whenNew() is not enough.
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);
// ...
}
}
Note: 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 for your use case.