Thursday, June 7, 2018

Mocking Non-Static Private Method Using PowerMockito

$\mathtt{RELATED\ TOPICS}$ @ Mocking Static Private method Mockito does not support the mocking of Private Methods. However, we can use PowerMockito that extends Mockito. Private methods may be mocked by following ways:

Test Class Setup

Test class should be anotated with @RunWith(PowerMockRunner.class) and @PrepareForTest({ClassWithPrivateMethod.class}).

When a class is annotated with @RunWith or extends a class annotated with @RunWith, JUnit will invoke the class it references to run the tests in that class instead of the runner built into JUnit [REF].

@PrepareForTest tells PowerMockito to prepare listed 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 and also classes that should be return a mock object upon instantiation.

Mocking Private Method

A Private Method can be mocked in following way:

  ClassWithPrivateMethod instance = new ClassWithPrivateMethod();
  ClassWithPrivateMethod spy = PowerMockito.spy(instance);  // IMPORTANT its NOT Mockito.spy()

  PowerMockito.doReturn().when(spy, "PRIVATE_METHOD_NAME", );
  

Example

Consider a following utility class.

  public class StockDetails {

    //------------------------------------------------------------------------

    /**
     * Returns details of Stock corresponding to stockNumber
     * @param stockNumber stock number whose detail is to be found
     * @return array of strings representing stock detail
     *           array[0] = stockNumber
     *           array[1] = Stock Description
     *           array[2] = price
     *         null if stock detail is not found
     */
    public String[] getStockDetails(String stockNumber) {
      try {
        String stockDetail = requestStockDetails(stockNumber);

        String[] components = stockDetail.split("---");
        if (components[0].equals("SUCCESS")) {
          return new String[]{stockNumber, components[1], components[2]};
        }
      } catch (Exception e) {}
      return null;
    } // getStockDetails

    //------------------------------------------------------------------------

    /**
     * Retrieves the stock details from the stock information
     * server
     *
     * @param stockNumber
     * @return stock detail in json format
     */
    private String requestStockDetails(String stockNumber) {
      String stockDetail = "";

      //
      // logic for making HTTP request or any other request
      // made to the server to retrieve the stock detail
      //

      return stockDetail;
    } // requestStockDetails

    //------------------------------------------------------------------------

  } // StockDetails

  
Here I am going to write JUnit method to verify the method getStockDetails() which depends upon the private method requestStockDetails().

This private method makes an HTTP request to retrieve some results. We don't want the real HTTP request made for the unit test. So, we will need to mock this private method. Following is the implementation of the JUnit test which mocks the private method.

    import junit.framework.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.powermock.api.mockito.PowerMockito;
    import org.powermock.core.classloader.annotations.PrepareForTest;
    import org.powermock.modules.junit4.PowerMockRunner;

    /**
     * @author Sabbir Manandhar
     * @version 1.0
     */
    @RunWith(PowerMockRunner.class)
    @PrepareForTest({StockDetails.class})
    public class StockDetailsTest {

      @Test
      public void testGetStockDetails() throws Exception {
        String stockNumber = "LKSJf289447OIOIJ";
    
        StockDetails stockDetails = new StockDetails();
        StockDetails spy = PowerMockito.spy(stockDetails); // Make SURE you use PowerMockito.spy()

        PowerMockito.doReturn("SUCCESS---This is the most active stock last week---2500.00")
                .when(spy, "requestStockDetails", stockNumber);

        String[] details = spy.getStockDetails(stockNumber); // This will execute actual method getStockDetails() in stockDetails instance,
                                                             // but requestStockDetails() inside this method will be mocked result
        Assert.assertEquals(stockNumber, details[0]);
        Assert.assertEquals("This is the most active stock last week", details[1]);
        Assert.assertEquals("2500.00", details[2]);
      }
    } // StockDetailsTest

  

Warning - Using Mockito.spy() instead of PowerMockito.spy()

Its important that you use PowerMockito.spy() while creating spy object for the actual stockDetails instance in the test method.

If you use Mockito.spy() method, then you are going to get an Exception.

   java.lang.NullPointerException
      at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.addAnswersForStubbing(PowerMockitoStubberImpl.java:67)
       at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.prepareForStubbing(PowerMockitoStubberImpl.java:124)
       at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:92)
       at com.hogwart.StockDetailsTest.testGetStockDetails(StockDetailsTest.java:34)
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
       at java.lang.reflect.Method.invoke(Method.java:498)
       at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:66)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:310)
       at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:86)
       at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:94)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:294)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:127)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:282)
       at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
       at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:207)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:146)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:120)
       at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
       at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
       at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:122)
       at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:106)
       at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:53)
       at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
       at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
       at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
       at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
       at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
       at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)








No comments:

Post a Comment