Introduction to JMockit


Using mock-objects is crucial when it comes to unit-testing enterprise applications. Mocks prevent you from implicitly testing parts of your application many times. This not only leads to faster running times of the testsuites. It also improves time to find the cause of a bug on failure. Let me introduce to you JMockit, a Java mocking library which integrates nicely with JUnit and TestNG. As an open source library JMockit is released under the MIT license. It heavily relies on bytecode instrumentation, first introduced in Java SE 5. What does this mean? JMockit directly wires the mock-objects into the applications bytecode at runtime. Therefor it’s possible to mock any object even those not implementing any interface. This is one of the main advantages over proxy-based mocking libraries.

Instrumentation is done by a so called java-agent. You have to put a VM argument to activate the agent before running your tests with JMockit:

-javaagent:jarpath/jmockit.jar

Let’s start with an example to demonstrate the usage of JMockit. Think of the following scenario: The domain model contains a class Customer which is persistable to a database by the data access object CustomerDao. There exists some CustomerDaoTest to do integration testing of the persistence layer with some embedded database (this could be subject to another blog entry). Our example consists of a service facade containing the class CustomerService which uses the CustomerDao to read customers from the database, apply some business logic and then write changes to the database before returning the customers.

public class CustomerService {
  private CustomerDao dao;

  public List<Customer> doBusiness() {
    List<Customer> customers = dao.findCustomers();
    for (Customer customer : customers) {
      // do something with the customer
      dao.save(customer);
    }
    return customers;
  }
}

Using the real CustomerDao object in the CustomerServiceTest unittest would result in implicitly testing the DAO. As I mentioned before this is already done in CustomerDaoTest. To avoid this issue we’re mocking the CustomerDao, thus preventing the CustomerServiceTest to access the database.

public class CustomerServiceTest {
  @MockClass(realClass = CustomerDao.class)
  public static class MockCustomerDao {
    @Mock
    public List<Customer> findCustomers() {
      return buildDummyData();
    }

    private List<Customer> buildDummyData() {
      // builds some dummy data programmatically
      return ...;
    }

    @Mock
    public void save(Customer customer) {
      // nothing here
    }
  }

  private CustomerService service;

  public void testDoBusiness() {
    Mockit.setUpMocks(MockCustomerDao.class);
    List<Customer> customers = service.doBusiness();
    // insert the asserts here to ensure doBusiness() is correct
  }
}

So, what’s going on here? MockCustomerDao is implemented as a static inner class and get’s annotated with the appropriate annotations. It doesn’t have to implement any interface but you have to ensure that the mocked methods have the same signature as the original methods. The call of Mockit#setUpMocks() hints the java-agent to instrument the bytecode for this method call. When the testDoBusiness() method has finished the original bytecode will be resetted. So there won’t be any side effects to other test methods. Running the testDoBusiness() test method results in just testing the business logic implemented in the service method without ever accessing the database.

There’s one thing you have to consider while testing your code with mock objects: if you set up your unittests with mock objects your tests are bound to the implementation of the tested method in one way or another. That makes your unittests more fragile. It may be the case that some unittest will be broken if you change the implementation of the tested method. Feel free to share your thoughts about this via comments. :)

JMockit contains many more concepts to make mocking and testing even more convient. The API contains utilities for expectations, verifications, test coverage and hibernate emulation. So come in and find out. :)

Read More