Thursday, May 3, 2018

PowerMockito - Mocking One Static Method and Not Mocking Other in the Same Class

Suppose we have a class,

  public class ClassWithPrivateStaticMethods {
  
   private static String getParam(String paramName) {
      String result = "";

      ...
      ...

      return result;
   }

   private static String getDetail(String contextName) {
      String result = "";

      String temp = getParam(tempParamName);
      ...

      return result;
   }
}

Until today, to mock static method, I had been doing:

  PowerMockito.mockStatic(ClassWithPrivateStaticMethods.class);
  PowerMockito.when(ClassWithPrivateStaticMethods.class, "getParam", Mockito.anyString()).thenReturn("dummy");

This works only when your test executes only this static method getParam().

Problem

PowerMockito.mockStatic() actually mocks all the static method in the class.

So if you have the circumstance where you want to mock one static method, but you want other method to run normally, then this method will not work. Because the other method will also be mocked.

  PowerMockito.mockStatic(ClassWithPrivateStaticMethods.class)
  PowerMockito.when(ClassWithPrivateStaticMethods.class, "getParam", Mockito.anyString()).thenReturn("dummy");
  String result = Whitebox.invokeMethod(ClassWithPrivateStaticMethods.class, "getDetail", Mockito.anyString());

here result will not the result of the execution of the method getDetail() but result of mocked up which is either null or equivalent because we haven't defined mock for the method getDetail

Solution - Partial Mocking

We can solve this by mocking individual static methods by following way:

  PowerMockito.spy(ClassWithPrivateStaticMethods.class);
  PowerMockito.doReturn("dummy").when(ClassWithPrivateStaticMethods.class, "getParam", Mockito.anyString())
  String finalResult = Whitebox.invokeMethod(ClassWithPrivateStaticMethods.class, "getDetail", Mockito.anyString());

Complete Code Demonstration

Following is a complete code demonstration.
/* File: StaticMethodsTest.java
* Created: 2018-05-03
* Author: Sabbir Manandhar
*
* Copyright (c) 2018 Hogwarts.
*/
package com.worldlingo;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
/**
* JUnit test cases using PowerMockito for the Static Methods in a Class
*
* @author Sabbir Manandhar
* @version 1.0
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({TestClass.class})
public class StaticMethodsTest {
/**
* PowerMockito.mockStatic(); mocks all the static methods in a class
* So even you explicitly define mocked return for the static method,
* execution of the method in the test will still be mocked and won't execute
* its actual code. Instead it will return null (or appropriate value) if it
* has return value.
*
* @throws Exception
*/
@Test
public void test() throws Exception {
PowerMockito.mockStatic(TestClass.class);
PowerMockito.doReturn("TOM").when(TestClass.class, "toUpperCase", Mockito.anyString());
String result1 = TestClass.toUpperCase("I am the king");
String result2 = TestClass.toLowerCase("I am the king");
String result3 = TestClass.alterWords("I am the king");
Assert.assertEquals("TOM", result1);
Assert.assertNull(result2);
Assert.assertNull(result3);
} // test
//-----------------------------------------------------------------------------------------------
/**
* Similar to first test method
* All static methods will be mocked
* Here return value for each static methods are defined, and results are as expected
*
* @throws Exception
*/
@Test
public void test2() throws Exception {
PowerMockito.mockStatic(TestClass.class);
PowerMockito.doReturn("TOM").when(TestClass.class, "toUpperCase", Mockito.anyString());
PowerMockito.doReturn("marvolo").when(TestClass.class, "toLowerCase", Mockito.anyString());
PowerMockito.doReturn("VoLdEmOrT").when(TestClass.class, "alterWords", Mockito.anyString());
String result1 = TestClass.toUpperCase("I am the king");
String result2 = TestClass.toLowerCase("I am the king");
String result3 = TestClass.alterWords("I am the king");
Assert.assertEquals("TOM", result1);
Assert.assertEquals("marvolo", result2);
Assert.assertEquals("VoLdEmOrT", result3);
} // test2
//------------------------------------------------------------------------------------------------
/**
* Mock individual Static method
* Here TestClass.toUpperCase() and TestClass.toLowerCase() are mocked
* but TestClass.alterWords() is not
* So TestClass.alterWords() will actually execute the original code which
* in turn invokes the methods toUpperCase() and toLowerCase(). Since these
* are mocked, mocked results will be returned.
*
* @throws Exception
*/
@Test
public void test3() throws Exception {
PowerMockito.spy(TestClass.class);
PowerMockito.doReturn("TOM").when(TestClass.class, "toUpperCase", Mockito.anyString());
PowerMockito.doReturn("marvolo").when(TestClass.class, "toLowerCase", Mockito.anyString());
String result1 = TestClass.toUpperCase("I am the king");
String result2 = TestClass.toLowerCase("I am the king");
String result3 = TestClass.alterWords("I am the king");
Assert.assertEquals("TOM", result1);
Assert.assertEquals("marvolo", result2);
Assert.assertEquals("TOM marvolo TOM marvolo ", result3);
} // test3
} // StaticMethodsTest
//-----------------------------------------------------------------------------------------
/**
* Test class with Static Methods
*/
class TestClass {
public static String toUpperCase(String input) {
return input.toUpperCase();
} // toUpperCase
//---------------------------------------------------------------------------------------
public static String toLowerCase(String input) {
return input.toLowerCase();
} // toLowerCase
//---------------------------------------------------------------------------------------
public static String alterWords(String input) {
String[] comps = input.split(" ");
StringBuilder sb = new StringBuilder();
boolean alt = true;
for (String comp : comps) {
if (alt) {
sb.append(toUpperCase(comp)).append(" ");
} else {
sb.append(toLowerCase(comp)).append(" ");
}
alt = !alt;
}
return sb.toString();
} // alterWords
//----------------------------------------------------------------------------------------
} // TestClass







No comments:

Post a Comment