Tuesday, 30 September 2008

EasyMock replacement, JMockit rocks!

A while ago I was working on pure Java program which was written by one of my colleague. It is not a web application and it does not rely on any injection framework like Spring. The program itself is structured with a bit complicated design pattern. What I have to do is to add an extra feature with perform logging when a certain condition happen. Although it is not difficult to write that extra piece of work, the difficult part is actually how to test my extra piece of work properly without changing any existing code.

Here is the idea of the work:

  private static Log log = LogFactory.getLog(aClass.class);

  public void doImportantStuff()
  {
    if (important)
    {
      if (log.isInfoEnabled())
      {
        log.info("log some important information");
      }
    }
  }


To do a proper testing to see whether the code is actually calling log4j/commonlog, I have to mock the log object. However, since log object is created with as an static. I will not be able to mock it.

I search the net for a while, luckily I was able to find this tool called "JMockit". The cool feature it has is you don't need to rely on any injection framework, you can mock whatever you like. Since it used java.lang.instrument package, which can actually read and change the byte code at runtime, I can easily create a test which mock the log object and replace it at runtime.

To illustrate of what I mentioned, here is a sample test:

  public class AClassTest
  {
    public void testTheCodeDoesLogWhenItIsImportant()
    {
      Mockit.redefineMethods(Log.class, MockLog.class);

      // code that make it important

      new AClass().doImportantStuff();

      assertTrue(MockLog.infoMethodHasBeenCalled);
    }
  }

  public static class MockLog
  {
    public static boolean infoMethodHasBeenCalled = false;

    public void info(String value)
    {
      infoMethodHasBeenCalled = true;
    }
  }


As you can see the above, by using Mockit.redefineMethods(), Log object has been replaced with MyLog object which redefine the info() method. As a result, you can check whether the log.info() has been called properly without changing any of original code.

If you use EasyMock framework, you usually have to set the mock object in order to see the expectation. However, as because of runtime byte code modification feature, this mocking framework does not rely on that mock injection. It seems this mocking framework is unbeatable. However, one tricky thing is whenever you run your test, you have to provide jvm argument which point to JMockit jar file, which is "-javaagent:jmockit.jar". However, it does not harm to provide javagent argument. In fact, it is very easy to do so even if you use maven.

For further information on JMockit, please refer to JMockit official website.