Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing PowerMock's @PrepareForTest programmatically?

I am using PowerMock to mock static methods in junit tests, typically done as follows:

@RunWith(PowerMockRunner.class) @PrepareForTest({Foo.class,Bar.class}) public class SomeUnitTest {    @Before   public void setUpTest() {     setUpFoo();     setUpBar();   }    private void setUpFoo() {     mockStatic(Foo.class);     when(Foo.someStaticMethod()).thenReturn(1);   }    private void setUpBar() {     mockStatic(Bar.class);     when(Bar.someStaticMethod()).thenReturn(2);   }    @Test   public void someTestCase() {     ...   }  } 

This works fine, but I'm finding that specifying the @PrepareForTest annotation is preventing me from making my testing API flexible.

What I'd like to do is something like the following:

public class MockLibraryOne {    public static void setUpLibraryOne() {     setUpFoo();     setUpBar();   }    private static void setUpFoo() {     mockStatic(Foo.class);     when(Foo.someStaticMethod()).thenReturn(1);   }    private static void setUpBar() {     mockStatic(Bar.class);     when(Bar.someStaticMethod()).thenReturn(2);   }  }  @RunWith(PowerMockRunner.class) public class SomeUnitTest {    @Before   public void setUpTest() {     MockLibraryOne.setUpLibraryOne();   }    @Test   public void someTestCase() {     ...   }  } 

Here my unit test has a dependency on LibraryOne, but it does not know which classes LibraryOne depends on, so it does not know which classes to add to the @PrepareForTest annotation.

I could make SomeUnitTest extend MockLibraryOne and add the @PrepareForTest annotation to the MockLibraryOne class, but I will have dependencies on more than just MockLibraryOne in other unit tests, so inheritance is not a general solution.

Is there some way of programmatically preparing a class for testing under PowerMock, instead of using the @PrepareForTest annotation? For example, something like the following:

public class MockLibraryOne {    public static void setUpLibraryOne() {     setUpFoo();     setUpBar();   }    private static void setUpFoo() {     prepareForTest(Foo.class);     mockStatic(Foo.class);     when(Foo.someStaticMethod()).thenReturn(1);   }    private static void setUpBar() {     prepareForTest(Bar.class);     mockStatic(Bar.class);     when(Bar.someStaticMethod()).thenReturn(2);   }  } 

I guess it would be nice if PowerMockRunner processed the @PrepareForTest annotation a little differently: for each specified class, it should not only add that class (and its hierarchy) to the list of classes to prepare for mocking, but then examine that class to see if it has any @PrepareForTest annotations as well:

@RunWith(PowerMockRunner.class) @PrepareForTest({MockLibraryOne.class}) public class SomeUnitTest {   ... }  @PrepareForTest({Foo.class,Bar.class}) public class MockLibraryOne {   ... } 

}

So in this the @PrepareForTest annotation on SomeUnitTest would find MockLibraryOne, and the @PrepareForTest annotation there would drag in Foo.class and Bar.class as well.

So perhaps writing my own test runner to replace PowerMockRunner may be a solution.

Or perhaps there's a simpler solution, using PowerMockAgent class, for example?

edit: Mock Policies may be one solution: https://code.google.com/p/powermock/wiki/MockPolicies

edit: Mock Policies works with PowerMockRunner but not (it seems) with PowerMockRule (which I sometimes require due to class loader issues).

like image 222
John Q Citizen Avatar asked Oct 28 '13 23:10

John Q Citizen


1 Answers

What you try to achieve will not work.

The problem is that powermock must rewrite the client class's code to intercept the static invocation and it can't do this after the class is loaded. Thus it can only prepare a class for test before it is loaded.

Let's assume you want to mock the System.currentTimeMillis invocation in the following simple class.

class SystemClock {     public long getTime() {         return System.currentTimeMillis();     } } 

Powermock will not change the code of java.lang.System.currentTimeMillis, because it can't. Instead it changes the SystemClock's byte code so that it does not invoke System.currentTimeMillis anymore. Instead it invokes some other object that belong to powermock.

This is how powermock get's full control over the return value and allows you to write a test like this:

@RunWith(PowerMockRunner.class) @PrepareForTest({ SystemClock.class }) public class PowerMockitoTest {      @Test     public void systemTimeMillis() {         SystemClock systemClock = new SystemClock();          PowerMockito.mockStatic(System.class);          PowerMockito.when(System.currentTimeMillis()).thenReturn(12345L);          long time = systemClock.getTime();         assertEquals(12345L, time);     } } 

You can see that powermock has rewritten the client class in the stacktrace of your debugger. Set a breakpoint at SystemClock.getTime and step into the invoked method.

Powermock stacktrace

As you can see SystemClock invokes a MockGateway.

If you take a look at the variables on the stack of the MockGateway invocation, you can see how the original System.currentTimeMillis method is handled.

debug variables

like image 200
René Link Avatar answered Sep 19 '22 16:09

René Link