“Most of the code is already written, we just need to add/enhance new feature”. Sounds familiar, Adding new features or fixing bug requires modifying legacy code. In my opinion, any code that does not have unit test is Legacy code. So code written last week without unit test is legacy code.
Some of “old” legacy code is very difficult to unit test because they have “rich” methods; I call methods having too many external dependency “rich” methods. In reality these methods need refactoring to extract utility like:

  • reading config file.
  • read DB and update DB
  • create a file
  • call a shell script to FTP file
  • update DB

Refactoring requires code change that comes with some risk that might not go well with Management and Users.
I have identified following artifacts during my experience with legacy code that might come handy while writing unit test for legacy code.

MOCKING “private methods” INVOCATION.

You can create Partial mock for class under test and set expectation for any methods that you may want to skip from unit test. Yes, any method including “private methods”


@Test
public void testLegacyMethod(){

ClassUnderTest classUnderTest = createPartialMock(ClassUnderTest.class, "privateMethodTakeStringAndReturnVoid", "privateMethodReturnObject");
//private method returning void
try {
PowerMock.expectPrivate(classUnderTest , "privateMethodTakeStringAndReturnVoid",(String)anyObject())anyTimes();
} catch (Exception e) {
     e.printStackTrace();
}
//private method returning Object
		try {
			PowerMock.expectPrivate(classUnderTest, "privateMethodReturnObject",(String)anyObject(),(StringBuffer)anyObject()).andReturn(new Object()).anyTimes();
		} catch (Exception e) {
			e.printStackTrace();
		}

expectLastCall();
//
// calling real method, this method is under test.
classUnderTest.legacyMethod();
//asserts
}

VALIDATING “parameters passed” TO MOCK OBJECT’s METHOD (ex: DB operation).

Validating data passed to method on mock object is good idea with legacy code where functional or automation testing might not be present to check data validation on each parameter.


@Test
public void testParameterValidation(){
MyClassUnderTest test = new MyClassUnterTest();
try {
DataAccessInterface mockDA = createMock(DataAccesslnterface.class);
//set instance variable (no public setter in legacy code) 
Whitebox.setInternalState(test ,"dataAccess", mockDA);
mockDA.createOrder((Order)anyObject());
//we can validate data passed to createOrder() using ".andDelegateTo"
expectLastCall().andDelegateTo(new DataAccessValidations());

//lets test now, buy() will construct order object and call createOrder on DataAccessInterface.
test.buy();
}catch(Execption e){}

Implement validation code in DataAccessValidations.

public class DataAccessValidations implements DataAccessInterface{

public void createOrder(Order order)  {
assertNotNull("'order' can not be null.",order);
assertNotNull("'order date' should not be null.",orde.getDate());
//other assertion can go here..
}

SUPPRESSING static INITIALIZATION.

You can see many static initializations in legacy code mostly to initialize resources needed for the class. (ex: Config files, DBConnection, Files etc). Refactoring code to use dependency injection could be good idea but we can still test this code by suppressing the static method or initialization.


import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.core.classloader.annotations.PrepareForTest;

@RunWith(PowerMockRunner.class)
@PrepareForTest({MyClassUnderTest.class})
//This will stop all static initilization.
@SuppressStaticInitializationFor({"com.mycompany.MyClassUnderTest"})

public class MyUnitTest{

public void myTest(){
//use Whitebox to assign mocked object to static variables.
}
}