"Untestable" code - JMockit

This is a part of "Untestable code" series. See the introduction to know what is it all about (yes, you really should go there, do it).

The main idea of the series is to write unit-tests for a particularly nasty piece of code. In this part I will use JMockit library to write test cases.

Please notice the source code attached at the end of this text.

It's worth mentioning here, that this "particularly nasty piece of code" which is the base of all articles from the "untestable code" series is taken from JMockit tutorial. ...what's even more important, the test classes are taken from JMockit tutorial as well, which means I am NOT an author of these classes - they were created by Rogério Liesenfeld.

JMockit

JMockit is a bunch of test-related projects created by Rogério Liesenfeld. I don't aim at evaluating all of them - I only take what's needed to solve the problem. For more info about JMockit see the project homepage.

Original class

This is the class under test:

public final class ServiceA {

    public void doBusinessOperationXyz(EntityX data)
        throws InvalidItemStatus {

        List items = Database.find(
            "select item from EntityY item where item.someProperty=?",
            data.getSomeProperty());
        BigDecimal total = new ServiceB().computeTotal(items);
        data.setTotal(total);
        Database.save(data);
    }

}

Changing the original class

There is no need to change the original class. Even final keyword can stay as it is - JMockit doesn't need to create subclasses of mocked classes, so it doesn't care about final.

Test class

public class ServiceATest extends TestCase
{
   private boolean serviceMethodCalled;

   public static class MockDatabase
   {
      static int findMethodCallCount;
      static int saveMethodCallCount;

      public static void save(Object o)
      {
         assertNotNull(o);
         saveMethodCallCount++;
      }

      public static List find(String ql, Object arg1)
      {
         assertNotNull(ql);
         assertNotNull(arg1);
         findMethodCallCount++;
         return Collections.EMPTY_LIST;
      }
   }

   protected void setUp() throws Exception
   {
      super.setUp();
      MockDatabase.findMethodCallCount = 0;
      MockDatabase.saveMethodCallCount = 0;
      Mockit.redefineMethods(Database.class, MockDatabase.class);
   }

   public void testDoBusinessOperationXyz() throws Exception
   {
      final BigDecimal total = new BigDecimal("125.40");

      Mockit.redefineMethods(ServiceB.class, new Object() {
         public BigDecimal computeTotal(List items)
         {
            assertNotNull(items);
            serviceMethodCalled = true;
            return total;
         }
      });

      EntityX data = new EntityX(5, "abc", "5453-1");
      new ServiceA().doBusinessOperationXyz(data);

      assertEquals(total, data.getTotal());
      assertTrue(serviceMethodCalled);
      assertEquals(1, MockDatabase.findMethodCallCount);
      assertEquals(1, MockDatabase.saveMethodCallCount);
   }

   public void testDoBusinessOperationXyzWithInvalidItemStatus()
   {
      Mockit.redefineMethods(ServiceB.class, new Object() {
         public BigDecimal computeTotal(List items) throws InvalidItemStatus
         {
            throw new InvalidItemStatus();
         }
      });

      EntityX data = new EntityX(5, "abc", "5453-1");

      try {
         new ServiceA().doBusinessOperationXyz(data);
         fail(InvalidItemStatus.class + " was expected");
      }
      catch (InvalidItemStatus ignore) {
         // ok, test passed
         assertNull(data.getTotal());
         assertEquals(1, MockDatabase.findMethodCallCount);
         assertEquals(0, MockDatabase.saveMethodCallCount);
      }
   }
}

This one looks rather odd for people who (like me) are used to use some mock framework to do the dirty work for them. MockDatabase is a test-spy written by hand, which I find evil or at least cumbersome - why write something that can be written automatically ? Believe me or not, but it's not laziness which makes me put this question. I am deeply convinced that unit tests should be as easy as they can be, therefore I try not to put any logic - even the simplest one - into it.
Same goes for redefined methods computeTotal.

The magic resides in lines like these:
Mockit.redefineMethods(Database.class, MockDatabase.class);
Mockit.redefineMethods(ServiceB.class, ...);
What does it mean ? Exactly what it says - "don't use real Database, use MockDatabase instead", "don't use original computeTotal method of ServiceB class, use the one that I give you here", and so on.

How does it work ?

Behind the scene, JMockit depends on the JVM class redefinition mechanism. It's really amazing ! - see the sources of JMockit-core (yes, go and see for yourself right now), there are only few classes there, but the whole thing is very powerful.

Pros and Cons

Pros

  1. There is no need to change class under test - even final keyword is not a problem (which is unique feature of JMockit - I haven't seen any other mock-framework capable of this).
  2. Code coverage with cobertura works fine. (It's good to know, that JMockit comes with it own coverage tool - see JMockit Coverage for details - I haven't tried it out yet).

Cons

  1. Need to write mocks (test-spies) by hand, which means two things:
    • test class is big
    • some logic must be introduced into the test class

My 3 cents

I'm impressed by the JMockit's ability to mock final classes. But in fact, I don't recall that I missed this feature very often. What I don't like is that you have to write mocks by hand - I think it's a step back from the path drawn by jmock, easymock and others.

JMockit artifacts

As for now (21/04/2008, last JMockit version is 0.94c) there are no JMockit jars available on mvnrepository.com, so you need to download it from the project site and install in your local repo by hand.

Attachmets

The file with maven project is attached at the end of this text. Unzip and run with mvn test site. Results of tests will be printed directly to the console. Code coverage report is available in target/site directory (see index.html in there).

Links

All links gathered here.

AttachmentSize
uc-jmockit.zip10.93 KB

Unreadable tests for untestable code...

JMockit is clearly efficient to test ugly code but an ugly implementation must be refactored because otherwise any future modification of implementation will cost time. Ugly implementation code implies often ugly test code...
Moreover, the goal of unit tests is not only to prevent regression, it's also to document usability of the implementation code.
In the case of such an ugly implementation to test, you'd better to refactor by suppressing static method and to use Mockito for mocking.
With Mockito, you will have tests more readable and understandable. It's a clearly better solution...
Olivier Catteau
http://www.stateofmind.fr

Hello Olivier, my goal was to

Hello Olivier,

my goal was to show many different approaches that one can take in order to deal with such nasty code. I don't say JMockit is the best way to go. Please see the introduction (http://kaczanowscy.pl/tomek/2008-01/untestable-code-introduction) to learn about other options that were also described. JMockit is just one of many options that can be used.

--
Cheers,
Tomek Kaczanowski

 
 
 
This used to be my blog. I moved to http://tomek.kaczanowscy.pl long time ago.

 
 
 

Please comment using