Java - Testing - Mocks

java

Articles
Mock libraries

https://en.wikipedia.org/wiki/Mock_Object
http://www.mockobjects.com/
http://xunitpatterns.com/Test%20Spy.html

How can we test DAO code using a mock approach?

In general, the key to writing testable database code is to separate logic from access. For example, a DAO class should not encapsulate both the code for querying data over JDBC and the code for obtaining the JDBC connection. Lets begin by looking at some untestable code:

public class MyNonTestableUserDAO implements UserDAO {

    private Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
            "jdbc:mckoi://localhost/",
            "admin_user",
            "aupass00");
    }

    public User createUser(String userId, String firstName, String lastName) throws DAOException {
        try {
            PreparedStatement ps = getConnection().prepareStatement(SQL_INSERT);
            ps.setString(1, userId);
            ps.setString(2, firstName);
            ps.setString(3, lastName);
            ps.executeUpdate();
            ps.close();
            return new User(userId, firstName, lastName);
        } catch (SQLException e) {
            throw new DAOException(e.getMessage());
        }
    }
}

The above code is tightly coupled. It contains both the code that query for data, and the code that connect to the database. If we were to test the above code, without any modification to the code, we would need to setup a real database, do data loading, and clean-up, which can be slow and time consuming.

The problem in testing the above DAO class is that unless we can replace the JDBC connection implementation, running the test successfully would require a real database with the right data. Now, how do we manage to do that?

We can intercept the getConnection() call with the help of AspectJ or other AOP frameworks, but that's too much work and results in unnecessarily complex code. Also, we could consider making the getConnection() method protected, and subclassing the DAO class in the test code overriding that particular method, which is already a pretty clean and compact solution:

public class MyTestableUserDAO1 implements UserDAO {

    protected Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
            "jdbc:mckoi://localhost/",
            "admin_user",
            "aupass00");
    }

    public User createUser(String userId, String firstName, String lastName)
        throws DAOException {
        try {
            PreparedStatement ps = getConnection().prepareStatement(SQL_INSERT);
            ps.setString(1, userId);
            ps.setString(2, firstName);
            ps.setString(3, lastName);
            ps.executeUpdate();
            ps.close();
            return new User(userId, firstName, lastName);
        } catch (SQLException e) {
            throw new DAOException(e.getMessage());
        }
    }
}

public class TestMyTestableUserDAO1 extends TestCase {

    public void testCreateUser() {
        // configure a mock implementation for the java.sql.Connection interface
        final MockConnection mock = new MockConnection();
        mock.setExpectedCloseCalls(0);
        mock.setupAddPreparedStatement(new MockPreparedStatement());

        // replacing the real Connection implementation with
        // a mock implementation
        UserDAO dao = new MyTestableUserDAO1() {
            protected Connection getConnection() {
                return mock;
            }
        };

        // exercise the class under test and assert expectations
        User user = dao.createUser("laskos", "Lasse", "Koskela");
        assertNotNull(user);
        assertEquals("laskos", user.getUserId());
        assertEquals("Lasse", user.getFirstName());
        assertEquals("Koskela", user.getLastName());

        // afterwards, we can check with the mock implementation that the
        // class under test collaborated with it as expected
        mock.verify();
    }
}

Notice that in the above code, the test code used mock. While this approach works, it is not the best solution, because we have to create an extra class (we have to create a sub-class of the original class).

Often the best approach, in my opinion, is to fix the root problem — the bad encapsulation. Once the logic inside getConnection() is moved out of the class under test, it is trivial to pass in a mock implementation in the unit test code instead of the real thing.

public class MyTestableUserDAO2 implements UserDAO {

    private Connection connection;

    public MyTestableUserDAO(Connection connection) {
        this.connection = connection;
    }

    public User createUser(String userId, String firstName, String lastName)
        throws DAOException {
        try {
            PreparedStatement ps = connection.prepareStatement(SQL_INSERT);
            ps.setString(1, userId);
            ps.setString(2, firstName);
            ps.setString(3, lastName);
            ps.executeUpdate();
            ps.close();
            return new User(userId, firstName, lastName);
        } catch (SQLException e) {
            throw new DAOException(e.getMessage());
        }
    }
}

public class TestMyTestableUserDAO2 extends TestCase {

    public void testCreateUser() {
        // configure a mock implementation for the java.sql.Connection interface
        final MockConnection mock = new MockConnection();
        mock.setExpectedCloseCalls(0);
        mock.setupAddPreparedStatement(new MockPreparedStatement());
        ...

        // replacing the real Connection implementation with
        // a mock implementation
        UserDAO dao = new MyTestableUserDAO2(mock);

        // exercise the class under test and assert expectations
        User user = dao.createUser("laskos", "Lasse", "Koskela");
        assertNotNull(user);
        assertEquals("laskos", user.getUserId());
        assertEquals("Lasse", user.getFirstName());
        assertEquals("Koskela", user.getLastName());

        // afterwards, we can check with the mock implementation that the
        // class under test collaborated with it as expected
        mock.verify();
    }
}

In the above code, we use constructor-based dependency injection, so that we can pass in a mock JDBC implementation so that we can test the rest of the DAO class without using a real database.

Note that even though this example hands an instance of java.sql.Connection to the DAO implementation, it could just as easily be a javax.sql.DataSource or some custom interface for ultimately obtaining a JDBC connection.

How can we test DAO code using the sandbox approach?

This option uses a real database. A great tool for this alternative method of testing is dbUnit, which allows allows the developer to create a data set, which is automatically created into the real database before running the test code and can clean up its mess afterwards if necessary.

public class MyNonTestableUserDAO implements UserDAO {

    private Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
            "jdbc:mckoi://localhost/",
            "admin_user",
            "aupass00");
    }

    public User createUser(String userId, String firstName, String lastName)
        throws DAOException {
        try {
            PreparedStatement ps = getConnection().prepareStatement(SQL_INSERT);
            ps.setString(1, userId);
            ps.setString(2, firstName);
            ps.setString(3, lastName);
            ps.executeUpdate();
            ps.close();
            return new User(userId, firstName, lastName);
        } catch (SQLException e) {
            throw new DAOException(e.getMessage());
        }
    }
}

public class TestMyNonTestableUserDao extends DatabaseTestCase {

    private static final String TESTDATA_FILE =
        "TestMyNonTestableUserDao-dataset.xml";

    public TestMyNonTestableUserDao(String testName) {
        super(testName);
    }

    // dbUnit uses this method to obtain a connection to the database which
    // it is supposed to set up as a sandbox for the actual test methods
    protected IDatabaseConnection getConnection() throws Exception {
        Class driverClass = Class.forName("com.mckoi.JDBCDriver");
        String url = "jdbc:mckoi://localhost/";
        String usr = "admin_user";
        String pwd = "aupass00";
        Connection jdbcConnection = DriverManager.getConnection(url, usr, pwd);
        return new DatabaseConnection(jdbcConnection);
    }

    // dbUnit uses this method to obtain the set of data that needs to be
    // inserted into the database to set up the sandbox
    protected IDataSet getDataSet() throws Exception {
        return new FlatXmlDataSet(new FileInputStream(TESTDATA_FILE));
    }

    public void testCreateUser() throws Exception {
        UserDAO dao = new MyNonTestableUserDAO();
        User user = dao.createUser("laskos", "Lasse", "Koskela");
        assertNotNull(user);
        assertEquals("laskos", user.getUserId());
        assertEquals("Lasse", user.getFirstName());
        assertEquals("Koskela", user.getLastName());
        makeSureUserWasInserted(user);
    }

    private void makeSureUserWasInserted(User user)
        throws AssertionFailedError, Exception {
        Connection jdbcConnection = getConnection().getConnection();
        // actual verification emitted for brevity ...
    }
}

Note that the test data is located in an XML file named MyNonTestableUserDAO-dataset.xml in the local filesystem:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <MY_USERS USER_ID='fb' FIRST_NAME='Foo' LAST_NAME='Bar'/>
  <MY_USERS USER_ID='tgpaul' FIRST_NAME='Thomas' LAST_NAME='Paul'/>
  <MY_USERS USER_ID='efh' FIRST_NAME='Ernest' LAST_NAME='Friedmann-Hill'/>
</dataset>

How should we write unit test using mocks?

Consider this code:

public class FinalInvoiceStep {
  private PrinterService printerService = null;
  private EmailService emailService = null;

  public FinalInvoiceStep(PrinterService printerService, EmailService emailService) {
    this.printerService = printerService;
    this.emailService = emailService;
  }

  public void handleInvoice(Invoice invoice, Customer customer) {
    if(customer.prefersEmails()) {
      emailService.sendInvoice(invoice,customer.getEmail());
    } else {
      printerService.printInvoice(invoice);
    }
  }
}

and the unit tests for the above code:

public class FinalInvoiceStepTest {
  private FinalInvoiceStep finalInvoiceStep = null;
  private Customer customer = null;
  private Invoice invoice = null;

  @Before
  public void beforeEachTest() {
    customer = new Customer();
    finalInvoiceStep = new FinalInvoiceStep(Env.PrinterServiceLocator(),     Env.EmailServiceLocator());
    invoice = new Invoice();
  }

  @Test
  public void normalCustomer() {
    customer.wantsEmail(true);
    finalInvoiceStep.handleInvoice(invoice, customer);
  }

  @Test
  public void customerWithPrintedInvoice() {
    customer.wantsEmail(false);
    finalInvoiceStep.handleInvoice(invoice, customer);
  }
}

The test cases above works but have a few issues:

  1. The test invoice has been printed (very bad, waste paper)
  2. No assertion was used.

To address the "printing" issue above, Matt decides to write a new class that is only used for testing:

public class DummyPrinterService  implements PrinterService{
  boolean anInvoiceWasPrinted = false;
  @Override
  public boolean isPrinterConfigured() {
    return true;
  }
  @Override
  public void printInvoice(Invoice invoice) {
    anInvoiceWasPrinted = true;
  }
  public boolean anInvoiceWasPrinted() {
    return anInvoiceWasPrinted;
  }
}

The above class is a dummy class (a mock). This new class is just for testing, that is to emulate a fake printer service. It does not print anything. It just records that a print request was made. There is also an additional method that checks if an invoice was printed or not. With that class in place, Matt rewrote the unit tests as:

public class FinalInvoiceStepTestImproved {
    private FinalInvoiceStep finalInvoiceStep = null;
    private Customer customer = null;
    private Invoice invoice = null;
    private DummyPrinterService dummyPrinterService = null;

    @Before
    public void beforeEachTest() {
      dummyPrinterService = new DummyPrinterService();
      customer = new Customer();
      finalInvoiceStep = new FinalInvoiceStep(dummyPrinterService, Env.EmailServiceLocator());
      invoice = new Invoice();
    }

    @Test
    public void normalCustomer() {
      customer.wantsEmail(true);
      finalInvoiceStep.handleInvoice(invoice, customer);
      assertFalse("Nothing should be printed",dummyPrinterService.anInvoiceWasPrinted());
    }

    @Test
    public void customerWithPrintedInvoice() {
      customer.wantsEmail(false);
      finalInvoiceStep.handleInvoice(invoice, customer);
      assertTrue("Invoice was printed",dummyPrinterService.anInvoiceWasPrinted());
    }
}

The test cases work. However, after some time, Matt realizes that his solution does not really scale well because he also has to write a dummyEmailService. He actually needs to write a lot of classes like this during development. The code grows and he also has to keep track of all these dummy classes, since when the “real” classes change, all these dummy classes need updating too. Soon his needs for verifying results become more demanding and his dummy classes also contain code on the order of methods that were called and complex initializer methods.

In the example above, Matt was testing a method that had only arguments and no return value. There are also cases however where Matt had to test methods that returned complex business objects, further complicating the code of the “fake” classes.

After a while Matt starts fantasizing that he could have a magic wand to use to instantly:

  • Create fake objects from real classes/interfaces
  • Instrument the fake objects to respond with specific data for each method
  • Verify the execution of methods in those fake objects
  • Verify the arguments passed in the methods of those fake objects
  • Throw optional Exceptions for some calls

This magic wand would make his life much easier! Well, Matt is lucky because this magic wand exists and its called Mockito (one of several frameworks designed for this purpose–note: magic wand not included.

At RebelLabs, we sometimes say that smart + lazy = efficient. So why create a bunch of human error prone manual tests on-the-fly when you could be so much lazier and use a mocking framework, such as Mockito, EasyMock, JMockit or any other.

Mocking attempts to solve in an easy way the creation of fake objects that help the unit testing process. Mock objects sometimes remind me of the film “The Truman Show.” You use mocking in order to “fool” a Java object to think that it communicates with other real objects.

From the point of view of the tested class, everything runs normally. It communicates with other objects and gets all the appropriate responses. Behind the scenes, however, this is only theater, a mummer’s show if you will, which has been carefully planned by you.

The mock objects within your library allow you to setup a controlled environment with strictly-defined deterministic behaviours by all objects involved with the class in question.

In your new theater, you should use a mocking framework to set the stage, in which you carefully monitor all interactions of the tested class in order to verify its effectiveness. Then you can see if all your “actors” performing properly.

When should we use mocks?

Use mocks when the class you want to test communicates with other classes that have side effects. Some random examples of such side effects:

  • Charging a credit card or bank account
  • Printing medical records, invoices, personal documents, etc
  • Sending an overdue bill notification to a client via email
  • Sending a request to an external system
  • Launching weapons into a neighboring enemy solar system
  • Flooding the reactor core
  • Shutting down life support

We can also use mocking in other cases as well, such as classes that are very slow, and based on a lot of dependencies. Mocking is also great when we want to emulate strange errors such as a full hard disk, network failure, wrong serialized version, etc.

How can we use Mockito?

public class FinalInvoiceStepTestMocked {
    private FinalInvoiceStep finalInvoiceStep = null;
    private Customer customer = null;
    private Invoice invoice = null;
    private PrinterService printerService = null;
    private EmailService emailService =null;

    @Before
    public void beforeEachTest() {
      printerService = Mockito.mock(PrinterService.class);
      emailService = Mockito.mock(EmailService.class);
      customer = new Customer();
      finalInvoiceStep = new FinalInvoiceStep(printerService, emailService);
      invoice = new Invoice();
    }

    @Test
    public void normalCustomer() {
      customer.wantsEmail(true);
      finalInvoiceStep.handleInvoice(invoice, customer);
      //Nothing should be printed
      Mockito.verifyZeroInteractions(printerService); 
      //Something must be emailed 
      Mockito.verify(emailService).sendInvoice(invoice, customer.getEmail());
    }

    @Test
    public void customerWithPrintedInvoice() {
      customer.wantsEmail(false);
      finalInvoiceStep.handleInvoice(invoice, customer);
      //Nothing should be  emailed
      Mockito.verifyZeroInteractions(emailService); 
      //The invoice must be printed
      Mockito.verify(printerService).printInvoice(invoice); 
    }
}

Both Email and Printer Services are mocked. The FinalInvoiceStep class is then created with these two as constructor arguments. As far it is concerned it communicates with two real classes.

But in reality, we know that mocking is controlling these classes and observes all their input/output. Instead of assert methods, we use Mockito verify statements. These check that the appropriate method was called or not.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License