Introduction to mocking for unit tests

by Martin Monperrus

Mocking is a technique for better testing software. This post gives a short introduction of mocking, using examples in Java.

What is a mock?

There is no unique definition of “mock” (see [2,3] for various viewpoints). In this document, the working definition is:

Mocks are classes that mimic application classes and that are only used in test code.

Mock by example

First, let us consider a standard unit test of a web application.

  @Test
  void testWebApp() {
    WebApp app = new WebApp();
    app.createUser("Martin Monperrus", "University of Lille");
    assertEquals("University of Lille", app.getAffiliation("Martin Monperrus"));    
  } 

This test specifies that the user is correctly stored in the database, then retrieved using getAffiliation. Consequently, in addition to testing WebApp, it also tests the underlying default/normal implementation of the database (let’s call it DatabaseImpl, it implements the interface IDatabase). To avoid this, one can create an object that mocks the database as follows.

  @Test
  void testWebApp() {
    WebApp app = new WebApp();
  
    IDatabase mockDb = new DBMock();
    app.setDataBase(mockDb);
  
    // the user will be stored in the mock database
    app.createUser("Martin Monperrus", "University of Lille");
  
    assertEquals("University of Lille", app.getAffiliation("Martin Monperrus"));

  }

  // a mock key-value database 
  // instead of persisting to disk, all values  are stored in memory
  // here, the mock object is implemented as an anonymous class
  class DBMock implemenys IDatabase {
      private Map data = new Hashmap();
    
      @Override
      public void insert(String key, String value) {
        this.map.put(key, value);
      }
    
      @Override
      public String get(String key) {
        return this.map.get(key);
      }
  }

In this example, the mock enables the developer to completely specify the test environment.

Why mocks?

Mock objects mimic a default implementation. They have the same interface as the real objects. Consequently, the code under test remains unaware of whether it is using a real object or a mock object. There are two main reasons for creating test mocks.

1) Independence from dependencies: First, it enables testers to abstract over the actual dependencies of the application under test. In the above example, the test is independent of the actual implementation of the database that is used. This provide some advantages in many situations, for example when the actual implementation is too heavy-weight (memory, CPU, etc); when the actual implementation is non-deterministic; when the actual implementation is slow. With mocking, the test setup completely specifies the environment and avoids implicit assumptions or undesired side-effects.

2) Testability: Second, it enables to have finer grain assertions, hence to write better tests.

  @Test
  void testWebApp() {
  WebApp app = new WebApp();
  
  // this object will enable us to access the internal data of the mock object
  final Map internalData = new Hashmap();
  
  IDatabase mockDb = new IDatabase() {
    Map data = internalData; 
    .. the same code
  }
  app.setDataBase(mockDb);
  app.createUser("Martin Monperrus", "University of Lille");
  assertEquals("University of Lille", app.getAffiliation("Martin Monperrus"));
  
  // now we can also assert something on the internal state of the mock object
  // this is not possible with the actual implementations of IDatabase
  assertEquals(1, internalData.keys().size());
  } // end test method

When there are things that are untestable in default implementations (because of third-party code, visibility, encapsulation, etc), mocks enable testers to insert fields and methods that are specific to testing and they support writing the correct assertions (as the assertion on internalData in the above example).

How to implement mocks?

Mocks can be implemented from scratch or using a mocking framework.

1) Barebone: In the above example, mocking is possible thanks to dependency injection through the setter method setDataBase (WebApp does not depend on a concrete implementation of IDatabase, but only on the interface). Then, the use of an anonymous class is handy is this case. If the anonymous class gets too large, it is always possible to create a specific mock class (say MockDatabase).

2) Frameworks: There exists many mocking frameworks for all mainstream programming languages. For example, in Java there are JMock of Mockito. Why using frameworks if one can mock with barebone code? They simplify writing some interaction assertions against the mock objects (order and number of method calls, etc.). The added value is similar to that of using JUnit: it is very easy to write one’s own assertions but JUnit adds some great value.

Bibliography

  1. Mock objects (on Wikipedia)
  2. What is mocking? (on stackoverflow)
  3. Test Double
  4. Mocks Aren’t Stubs (by Martin Fowler)
  5. Mock Roles, not Objects (Steve Freeman, Nat Pryce, Tim Mackinnon, Joe Walnes)

Contributors

Thanks to Nicolas Petitprez, Erwan Douaille, Lionel Seinturier, Daniel Le Berre for their feedback on this page.

Tagged as: