Tuesday, May 4, 2010

How to mock static methods using PowerMock

OK, what are we up to today!  Today I will demonstrate how can we write unit test for a class that invokes a static method on another class to get some work done using PowerMocks (Mockito api) - Mocks on steroids! 

Yeah, Yeah I know, I know implementing functionality using static methods is bad and stuff like that.  But sometimes you just can't help it as its not in your control.  Hence, as all wise man (woman included) say, better deal with it!

We have not yet seen how we could mock normal classes and interface and directly looking at how to mock static methods.  Well as Mr. IronMan said

Sometimes you gotta run before you can walk.

Currently, I am working on a project that uses Spring Roo.  Roo uses a lot of AspetJ goodness.  It adds a lot of static methods to domain objects via AspectJ, making the domain objects richer.  For e.g. In a domain Class Customer it would add methods like findCustomer, findAllCustomers, countCustomers etc.  If you are interested in exploring more about Spring Roo, start here.

Spring Roo uses Spring MVC as the web layer.  Hence, I am going to take example of CustomerController (a controller that does CRUD on Customer domain object).  Enough history, lets see the real stuff!

Requirement:

When user requests to see the customer information, CustomerController should find the requested Customer and return that in the model.

Controller Code:

The controller code would look like

Not showing the Spring MVC specific annotations.  Here, findCustomer is the method added by Spring Roo to the Customer domain class.  This is a static method which  is invoked by CustomerController to get the requested Customer instance.  This is what we want to test. 

How will we test it - How do they do it!:

As I explained in my previous post, PowerMocks uses custom class-loader and byte-code manipulation to enable mocking of static methods.  PowerMocks does this by using the following annotations
  • @RunWith -- PowerMocks uses a custom Runner called PowerMockRunner.  This is done so that 
    • PowerMocks could show informative message if an exception is thrown from the test
    • PowerMocks could notify listeners of certain events.  For e.g. before the test methods start executing an event BeforeTestMethod is triggered.  Don't worry, if you don't understand this.  Its a little advanced stuff.  Just remember, that we need to use a Custom runner.
  • @PrepareForTest -- This annotation tells PowerMock to prepare certain classes for testing. Classes needed to be defined using this annotation are typically those that needs to be byte-code manipulated. This includes final classes, classes with final, private, static or native methods that should be mocked
That's about all the annotations you will need!  Next we will have to tell PowerMocks that we want to mock static methods of which class.  This is done using

We are saying PowerMocks (using the Mockito extension api) that we want to mock static methods in Customer class.

Next up, we have to stub the method we are interested in (we will be doing state based testing.  PowerMocks also supports interaction based testing, using the verify syntax.  Will not be showing it in this post though).  That's done like this

And then we can simply assert the customer is returned in the modelMap.  That's all!  Here's the complete test case code.

And we are done!  As we can see, PowerMocks (using the Mockito extension api) uses English like syntax, because of this test cases look a lot compact and are easier to read.

As usual, feedback, comments and complaints are always welcome!

11 comments:

  1. Faced couple of major problems, while implementing PowerMocks, on legacy code.
    First one, Each time I tried mocking static methods of a certain class, I got this:

    java.lang.SecurityException: Prohibited package name: java.net
    at java.lang.ClassLoader.preDefineClass(ClassLoader.java:479)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:614)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
    at org.powermock.core.classloader.DeferSupportingClassLoader.loadClass(DeferSupportingClassLoader.java:61)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
    at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
    at java.lang.Class.getDeclaredConstructors0(Native Method)
    at java.lang.Class.privateGetDeclaredConstructors(Class.java:2357)
    at java.lang.Class.getDeclaredConstructors(Class.java:1808)
    at org.mockito.internal.creation.jmock.ClassImposterizer.setConstructorsAccessible(ClassImposterizer.java:58)
    at org.mockito.internal.creation.jmock.ClassImposterizer.imposterise(ClassImposterizer.java:53)
    at org.powermock.api.mockito.internal.mockcreation.MockCreator.createMethodInvocationControl(MockCreator.java:83)
    at org.powermock.api.mockito.internal.mockcreation.MockCreator.mock(MockCreator.java:51)
    at org.powermock.api.mockito.PowerMockito.mockStatic(PowerMockito.java:69)
    at com.foundryinc.cust.gsi.frameworks.webstore_oracle.OrderImplTest.shouldAddFreeSamples(OrderImplTest.java:91)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

    ReplyDelete
  2. Hi Devesh,

    Could you please send me a snippet of original code you were trying to test?

    I will try and write a unit-test using PowerMocks (Mockito) api and let you know.

    ReplyDelete
  3. Hi,

    I implemented the same solution.
    This worked fine, thanks.

    But i have a controller which instanciate an new Object, then this crashed with a nullPointer.

    I reproduce the error with the test that follow:

    DeviceToken expected = new DeviceToken();
    PowerMockito.mockStatic(DeviceToken.class);

    // this throw an NullPointerException
    // at org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect.ajc$if_1(AnnotationBeanConfigurerAspect.aj:80)
    // at fr.mypackage.domaine.DeviceToken.(DeviceToken.java:21)
    DeviceToken tok2 = new DeviceToken();

    Mockito.when(DeviceToken.createOrRetrieve("2")).thenReturn(expected);

    DeviceToken found = DeviceToken.createOrRetrieve("2");
    PowerMockito.verifyStatic();
    assertThat(found.getToken(),is(nullValue()));


    any Idea?

    Thanks

    ReplyDelete
  4. Hi jhattat,

    I think you are also using Spring Roo or AspectJ to weave aspects into your domain class DeviceToken.

    You are mocking out static method of the DeviceToken class by using the statement "PowerMockito.mockStatic(DeviceToken.class);"

    After that you are creating a new instance of DeviceToken using the statement "DeviceToken tok2 = new DeviceToken();"

    This is a problem. Just change the order of those two statements
    i.e. instead of

    "PowerMockito.mockStatic(DeviceToken.class);
    DeviceToken tok2 = new DeviceToken();"

    change it to

    "DeviceToken tok2 = new DeviceToken();
    PowerMockito.mockStatic(DeviceToken.class);"

    Try this out and I think it should work.

    Do let me know what happens.

    Thanks

    Deep

    ReplyDelete
  5. Your code helped me with my problem. I was using the mockstatic before I was instantiating my objets. I also tried partial mocking on the methods I call but it seems aspectj add more magic methods behind the scenes.

    so this failed with some isAnnotationPresent error
    mockStatic(TransactionLog.class);
    mockStatic(OnlineShopShipping.class);

    TransactionLog transactionLog = new TransactionLog();
    OnlineShopShipping shipping = new OnlineShopShipping();

    but this is fine now with
    TransactionLog transactionLog = new TransactionLog();
    OnlineShopShipping shipping = new OnlineShopShipping();
    mockStatic(TransactionLog.class);
    mockStatic(OnlineShopShipping.class);

    ReplyDelete
  6. Hello Raf,

    Glad to know that it helped.

    ReplyDelete
  7. I used your setup but used easymock. My return object is coming back null though.

    //userData is null after mocking SessionManager.get()

    public BaseLogical loadPage(HttpServletRequest newRequest)
    throws LoggedException {
    getLogger().info("Inside the AgentProfilePageProcessor.loadPage()");
    AgentLogical agentLogical = new AgentLogical();
    try {
    UserData userData = (UserData) SessionManager.get(newRequest, SessionManager.USER_DATA);
    if (null != userData) {
    // rest of the method

    // test code --> I setup userData in a seperate method.


    PowerMock.mockStatic(SessionManager.class);

    ISessionDataObject userData = (ISessionDataObject) getUserData();

    EasyMock.expect(SessionManager.get(request, SessionManager.USER_DATA)).andReturn(userData);

    agentLogical = (AgentLogical) agentProfilePageProcessorControl.loadPage(request);

    ReplyDelete
  8. Hi There,

    Your setup looks right to me. Only thing I can think of is, did you add the annotations

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(SessionManager.class)

    on the test class?

    If you do not add those annotations, mocking static methods will not work.

    Let me know if this fixes the problem.

    If this does not fix the problem, please paste your full test code, I will try and find the issue myself.

    Thanks

    Deep Shah

    ReplyDelete
  9. Hi Deep,
    I am pasting my code could you please tell me whats the problem with this code as when I am mocking the static class using :
    PowerMock.mockStatic(DAOBaseHelper.class);

    Some InvocationTargetException is coming in PowerMockJUnit44RunnerDelegateImpl.

    Thanks
    Vijayendra Bhati

    ReplyDelete
  10. import mypkg;

    @RunWith (PowerMockRunner.class)
    @PrepareForTest ({DAOBaseHelper.class,ChannelServiceFactory.class})
    @PowerMockIgnore ({"org.apache.log4j.*"})

    public class DAOBaseHelperTest extends TestCase {

    /*** Junit method to execute getContactPointHeader() in DAOBaseHelper ***/
    @Test
    public void testGetContactPointHeader() {

    try {
    Map testContactPointHeaderMap = new HashMap();

    testContactPointHeaderMap = loadConfigurationMaps();
    System.out.println("Step 1 : After calling loadConfiguratinMaps()");

    //Populating static input data for HeaderData
    HeaderData inputHeaderData = populateHeaderData();
    System.out.println("Step 2 : After populating input data");

    //Set the class to be used for Mock - DAOBaseHelper
    PowerMock.mockStatic(DAOBaseHelper.class);
    System.out.println("Step 3.1 : After setting mockstatic classes");

    //Mock for private static method - loadConfigurationMaps()
    PowerMock.createPartialMock(DAOBaseHelper.class, "loadConfigurationMaps");
    PowerMock.expectPrivate(DAOBaseHelper.class);
    System.out.println("Step 5 : After setting mocks for private static method");

    PowerMock.field(DAOBaseHelper.class, "contactPointHeaderMap").set(DAOBaseHelper.class,testContactPointHeaderMap);
    System.out.println("Step 6 : After setting field value for contactPointHeaderMap");

    PowerMock.mockStatic(ChannelServiceFactory.class);
    PowerMock.createPartialMock(ChannelServiceFactory.class,"getChannelService");
    System.out.println("Step 3.2 : After setting mockStatic classes");
    //PowerMock.createPartialMock(ChannelServiceFactory.class,"getChannelService");
    //PowerMock.expectNew(ChannelService.class);
    PowerMock.field(DAOBaseHelper.class,"channel").set(ChannelService.class,"IBL");

    //EasyMock.expect(ChannelServiceFactory.getChannelService().getChannel()).andReturn("IBL");
    System.out.println("Step 4 : After setting expect value for ChannelService");

    //Replay mocked classes
    PowerMock.replay(DAOBaseHelper.class);
    PowerMock.replay(ChannelServiceFactory.class);
    System.out.println("Step 7 : After calling replay methods");

    DAOBaseHelper.getContactPointHeader(inputHeaderData);

    assertNotNull(inputHeaderData.getContactPointHeader());
    System.out.println("ZZZZZZ testGetContactPointHeader() :: AssertNotNull Successful");

    assertEquals("1234", inputHeaderData.getContactPointHeader().getApplicationId());
    System.out.println("ZZZZZZ testGetContactPointHeader() :: AssertEquals Successful");
    }
    catch(Exception exp) {
    System.out.println("ZZZZZZZ testGetContactPointHeader() :: Inside Catch block ::"+exp.getMessage());
    fail("Assertion Failed in testGetContactPointHeader");
    }


    }

    ReplyDelete
  11. Hello Vijendra,

    I think there are multiple problems with the way in which you are writing your test cases.

    But I will reserve my comments. Could you please also paste the code of ChannelService, ChannelServiceFactory, DAOBaseHelper, HeaderData and other utility methods used in your test code?

    After you do that, I will be in a better position to answer what exactly is wrong with your code.

    Thanks

    Deep Shah

    ReplyDelete

Have some Fun!