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 ClasspublicclassExampleClass {publicintgetHeight() {// Lets say we have a generic method which provides different result base on the provided idfinalCustomClass customClassInstance =newCustomClass();returncustomClass.getHeight(); }}
and here is a test case using whenNew() injection.
@RunWith(PowerMockRunner.class)@PrepareForTest({ExampleClass.class})publicclassExampleClassTest {privateExampleClass classUnderTest; @BeforepublicvoidsetUp() throwsException {// Create constructor of ExampleClass classUnderTest =newExampleClass(); } @TestpublicvoidgetHeight_shoudReturnHeightOfView() throwsNoSuchMethodException {finalint expectedHeight =100;// GivenfinalCustomClass 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);finalint actualResult =classUnderTest.getHeight();Assert.assertEquals(expectedHeight, actualResult); }}
Now let's come to the problem...
Let's say you have the following class to test:
// ClassToTestpublicclassClassToTest {// heavy methodprivatevoiddoInBackground() {newThread() { @Overridepublicvoidrun() {finalCustomClass customClassInstance =newCustomClass();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.
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.