Tuesday, January 15, 2008

Testing using Mock objects using Junit

In today's article,we'll talk about how to test our code using Mock objects using a small example.

Lets assume a class which uses lot of singleton classes for implementing the functionality. Singleton classes if used unwisely can clutter our program with too many static invocations and make our code hard to test. For this reason we should always de-couple these singleton instances to other objects/functions. For Example consider this class...

Public class AuthorizeAdapterImpl
{

LoggerManager _logMgr = null;
DataManager _dataMgr = null;
PlanManager _planMgr = null;

public AuthorizeAdapterImpl()
{ // constructor.
}

public boolean authorizeEverything()
{
_logMgr = LoggerManager.getInstance();
_dataMgr = DataManager.getInstance();
_planMgr = PlanManager.getInstance();

....
....
....
.... // code that extensively uses these variables.

}
}

Now, if you look closely at this class it is directly dependent on three Manager classes. So in order to run/test this class we need to make sure the other Manager classes which it depends on are initialized properly, which is totally unnecessary as we wanted to test logic in only this class.

We can address this in two possible ways..
  • Replace static invocations with simple getXXXInstance methods.
  • Dependency ingestion.
Lets look at the first alternative...

Replace static invocations with simple getXXXInstance methods :

The idea behind this is to move the singleton invocations into a separate private methods so that our function doesn't directly depend on those static methods directly. Look at the code below..

Public class AuthorizeAdapterImpl

{
LoggerManager _logMgr = null;
DataManager _dataMgr = null;
PlanManager _planMgr = null;

public AuthorizeAdapterImpl()

{ // constructor.

}

public boolean authorizeEverything()
{
_logMgr = getLoggerManagerInstance();
_dataMgr = getDataManagerInstance();
_planMgr = getPlanManagerInstance();
....
....
....
.... // code that extensively uses these variables.
}

protected LoggerManager getLoggerManagerInstance()
{
return LoggerManager.getInstance();
}

protected DataManager getDataManagerInstance()
{
return DataManager.getInstance();
}

protected PlanManager getPlanManagerInstance()
{
return PlanManager.getInstance();
}
}

Now, our 'authorizeEverything()' method doesn't depend on any of the singleton classes. This class now becomes testable because we can extend this class and override the getXXXInstance() methods to return our 'Mock' objects instead of actual collaborating objects like DataManager, LoggerManager etc..

public class MockAuthorizeAdapterImpl extends AuthorizeAdapterImpl
{
protected LoggerManager getLoggerManagerInstance()
{
return new MockLoggerManager();
}

protected DataManager getDataManagerInstance()
{
return new MockDataManager();
}

protected PlanManager getPlanManagerInstance()
{
return new MockPlanManager();
}
}

Now that we have replaced the collaborating objects with our mock objects we can use this class for testing the method
'authorizeEverything()'. This works because when we call this method we're actually invoking the method in the super class which is AuthorizeAdapterImpl in this case.

Dependency Ingestion :

Dependency ingestion is a refactoring technique that allows to do the things in a more nicer and efficient way. The basic way how this differs from the above given approach is that instead of abstracting away the singleton invocations into a seperate methods in the same class, we provide the instances of these classes as 'parameters' to this method/class. The choice is again left to the developer but in this case since all the singleton objects are class level variables it makes sense to inject them at the class level instead of passing them to each method.

Public class AuthorizeAdapterImpl
{
LoggerManager _logMgr = null;
DataManager _dataMgr = null;
PlanManager _planMgr = null;

public AuthorizeAdapterImpl(LoggerManager logger, DataManager dMgr , PlanManager pMgr)

{ // constructor.
this._logMgr = logger;
this._dataMgr = dMgr;
this._planMgr = pMgr;
}

public boolean authorizeEverything()
{
//_logMgr = LoggerManager.getInstance(); We don't need this !!
//_dataMgr = DataManager.getInstance();
//_planMgr = PlanManager.getInstance();

....
....
....
.... // code that extensively uses these variables.
}

}

With this change, the code has to be changed all the invocation points where we create the object for AuthorizeAdapterImpl class to send the required objects to this class.

This makes our test class very easy to write..

public class AuthorizeAdapterImplTest
{
public void testAuthorizeEverything()
{
AuthorizeAdapterImpl impl = new AuthorizeAdapterImpl(new MockLogger(), new MockdataMgr(), new MockPlanMgr());
impl.authorizeEverything();
// assertion code to verify our test case.
}
}

There's one disadvantage with this approach...

if our AuthorizeAdapterImpl class has to use a new singleton object, then we've to change the constructor to inject that object too which may not be feasible all the times. What we can do in this case is that we'll move all these singleton objects into a container and pass the reference to the container to this class. Lets call the container DLMSubSystem. The AuthorizeAdapterImpl class becomes like this.

public class AuthorizeAdapterImpl
{

DLMSubSystem _dlmSubSystem = null;

public AuthorizeAdapterImpl(DLMSubSystem subSystem)
{
this._dlmSubSystem = subSystem;
}

public boolean authorizeEverything()
{
LoggerManager logger = _dlmSubSystem.getLogger();
DataManager dataMgr = _dlmSubSystem.getDataManager();
....
...
}
}

Well, i hope you got the basic idea behind how to use refactoring and mock objects for unit testing code. In my next article i'll tell you more about what constitutes a Mock object and how to define behavior of mock object as per our requirement.



Powered by ScribeFire.

No comments: