Thursday, August 16, 2018

JUnit Test - Setting Private Field Using PowerMockito's Whitebox

Introduction

There might be a case when you need to write a JUnit test for a class method which depends upon some private field that is in turn set during the deployment from some disk file or database or Network source or any other source.

Now when you write the JUnit test for that method, the field is not set. You cannot set it because it is a private field. You could have have a setter method, but obviously you are not going to create a setter method just for the sake of testing.

Solution Using PowerMockito - Whitebox

Such a case can be easily handled using PowerMockito. PowerMockito framework gives us the leverage to set the private fields with some mocked values.

Consider the following class StockCache:
class StockCache {
private Map<String, String[]> cachedStocks;
//-------------------------------------------------------------------------------------
/**
* Populates the cached stocks list
*/
public void initializeCachedStocks() {
// implementation to populate the cache list
// could be from disk or network or any other
} // initializeCachedStocks
//-------------------------------------------------------------------------------------
/**
* Returns the stock details of a stock corresponding to the stock number
*
* @param stockNumber stock number of the stock of which we need the detail
* @return Stock Detail if it exists in the cache, otherwise NULL
*/
public String[] getStockDetail(String stockNumber) {
return this.cachedStocks.get(stockNumber);
} // getStockDetail
} // StockCache

Here we try to write JUnit test for the method getStockDetail(). This method depends upon the private field cachedStocks. This field is in turn set by the method initializeCachedStocks(). Now to test the method getStockDetail(), the private field needs to be set. We could have called the method initializeCachedStocks() to set the field. But the question is, do we want to call this method during a test? Well the answer depends upon the nature of this method. If it simply reads some static data and sets the field, then there is no problem executing it during the test. However, if it needs to access some network location or read database, then we don't want to execute it.

At such instance, we want to set some mocked value to this field. We could have created a setter method. However, as already mentioned above, it is not good idea to create a setter method just for testing.

We can set the private field using org.powermock.reflect.Whitebox.setInternalState method.
Whitebox.setInternalState(<StockCacheInstance>, <private_field_name>, cachedStock);

Complete Implementation

Following is the complete implementation for testing the method.
/* File: StockCacheTest.java
* Created: 8/17/2018
* Author: Sabbir Manandhar
*
* Copyright (c) 2018 Hogwart Inc.
*/
//=======================================================================================
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import java.util.HashMap;
import java.util.Map;
/**
* @author Sabbir Manandhar manandharsabbir@gmail.com
* @version 1.0
*/
@RunWith(PowerMockRunner.class)
public class StockCacheTest {
@Test
public void testGetStockDetail() {
// START Mocked cachedStock
Map<String, String[]> cachedStock = new HashMap<String, String[]>();
String stockNumber = "LAS3453SFDSFD";
String[] stockDetail = new String[]{stockNumber, "This is the most active stock last week", "2500.00"};
cachedStock.put(stockNumber, stockDetail);
stockNumber = "UTR3453SFDSFD";
stockDetail = new String[]{stockNumber, "The most successful", "644500.00"};
cachedStock.put(stockNumber, stockDetail);
stockNumber = "UIYR343SFDSFD";
stockDetail = new String[]{stockNumber, "Heavily beaten.", "-45400.00"};
cachedStock.put(stockNumber, stockDetail);
stockNumber = "BJKB923FDSFD";
stockDetail = new String[]{stockNumber, "Useless", "00.00"};
cachedStock.put(stockNumber, stockDetail);
// END Mocked cachedStock
StockCache stockCache = new StockCache();
Whitebox.setInternalState(stockCache, "cachedStocks", cachedStock); // SETS THE INTERNAL PRIVATE FIELD
stockDetail = stockCache.getStockDetail("LAS3453SFDSFD");
Assert.assertNotNull(stockDetail);
Assert.assertEquals("This is the most active stock last week", stockDetail[1]);
Assert.assertEquals("2500.00", stockDetail[2]);
stockDetail = stockCache.getStockDetail("UIYR343SFDSFD");
Assert.assertNotNull(stockDetail);
Assert.assertEquals("Heavily beaten.", stockDetail[1]);
Assert.assertEquals("-45400.00", stockDetail[2]);
} // testGetStockDetail
} // StockCacheTest
//=======================================================================================
class StockCache {
private Map<String, String[]> cachedStocks;
//-------------------------------------------------------------------------------------
/**
* Populates the cached stocks list
*/
public void initializeCachedStocks() {
// implementation to populate the cache list
// could be from disk or network or any other
} // initializeCachedStocks
//-------------------------------------------------------------------------------------
/**
* Returns the stock details of a stock corresponding to the stock number
*
* @param stockNumber stock number of the stock of which we need the detail
* @return Stock Detail if it exists in the cache, otherwise NULL
*/
public String[] getStockDetail(String stockNumber) {
return this.cachedStocks.get(stockNumber);
} // getStockDetail
} // StockCache







3 comments: