Unit Testing with Mock Objects

Unit Testing

A Java application consists (roughly) of several classes interacting.
The first step towards a reliable application is, therefore, to have well tested individual classes. This is precisely the purpose of unit testing: testing units of code in isolation from other units. In our case, we’ll consider the Java class as the unit of code.
The main advantage of testing the class in isolation is making sure that the potential errors we can stumble upon are due to defects in the class under test and not caused by defects on collaborating classes.

Achieving the isolation

However, classes work in cooperation with other classes, so, how can we isolate the tested class?
Well, the idea is substituting the collaborating classes with compatible classes whose implementation is so trivial that we can be confident on their correctness.
The instances of those classes are known as test doubles and, very often, they’re also called mock objects even if this is a little bit of a misnomer.

What should we substitute

Substituting every single collaborating object with a test double would certainly be overkilling. I guess no one would even think of replacing a String object with a mock object.
Choosing the candidates to be replaced is a matter of common sense. Most people would not use test doubles in the following cases:

  • Classes with deterministic behaviour from trusted third parties. The aforementioned String object would certainly fall into this category.
  • Classes with a trivial implementation. Think of data transfer objects that are mere bunches of properties.

Easing the pain of writing test doubles

Once we have identified the candidates to be replaced, it’s time to write the test doubles. But wait!, thanks to several available mocking frameworks, we can avoid most of the work.
Among those frameworks, my favourite is Mockito, which I find very flexible and feature-rich.
Let’s see what Mockito can do for us in this first phase.
First of all, it can create a mock object from an existing type like this:

CustomerDao customerDaoDouble = Mockito.mock(CustomerDao.class);

After this line, methods from customerDaoDouble either do nothing (void methods) or just return a trivial value (null, false, 0, etc.).
Sometimes this is enough but usually we’ll need a specific behaviour for our test to work. This is another situation in which Mockito can help us. We can easily specify the behaviour of our test doubles’ methods like this:

Mockito.when(customerDaoDouble.findOne(nonexistentCustomerId)).thenReturn(null);
[...]
Mockito.when(customerDaoDouble.findOne(existingCustomerId)).thenReturn(existingCustomer);

Now, whenever we invoke findOne with the parameter nonexistentCustomerId we will get null, and whenever we call findOne with the parameter existingCustomerId we will get the object existingCustomer.
The action of specifying the test doubles’ behaviour is commonly known as setting the expectations.

Replacing the real object

Now we have our test double ready. We just need to use it in place of the real object. Let’s see how.
First of all, our class under test can cooperate with other classes in several ways:

  • An object of the collaborating class is used as a parameter in a method of our class.
    In this case we can just pass the test double as a parameter:

    	stackUnderTest.push(mockedItem);
    	
  • An object of the collaborating class is used as a field in our class.
    Then we’ll have to install our test double using some method, usually with a setter or a constructor. Or even assign it directly if the field is not private.
    But what, might you ask, if there is no accessible method to set the field? Well, in this case I’d suggest to just write a setter method or a constructor to set it.
    If you’re concerned about the field being publicly writeable by any class, you can always make the method protected and place the test class in the same package under the test tree.
    Lastly, some may argue about the suitability of writing a method just to be used by test classes, but remember that designing for testability is widely seen as a good development practice.

    	[...]
    	CustomerDao customerDaoDouble = Mockito.mock(CustomerDao.class);
    	// Installing the test double in place of the real customer DAO
    	customerServiceUnderTest.setCustomerDao(customerDaoDouble);
    	// Exercise some customerServiceUnderTest's method
    	[...]
    	
  • An object of the collaborating class is created inside a method.
    This kind of objects cannot be replaced by test doubles. If we need to do it, some refactoring will be necessary: promote the local object to field and provide a setter method for it.

Verification

The purpose of tests is to verify the correctness of our software. There are two ways we can do that:

  1. State verification. Since the execution of our software under test changes the state of the system, a straightforward verification strategy lies on checking the state of the modified objects. A typical example of this strategy is the verification through assertions of the object returned by a method.
    	Assert.assertEquals(expectedObject, stackUnderTest.pop());
    	
  2. Behaviour verification. A different approach to verification consists in ensuring that our tested class interacts with its collaborating classes in the expected way.
    This approach is of particular interest in this article, since it’s another area where Mockito can help us. In particular, the provided method verify makes this kind of verification pretty easy.
    In its simplest use, this method is used like this:

    	Mockito.verify(testDouble).expectedMethod(expectedParameters);
    	

    A JUnit test with such a verification will pass only if the method expectedMethod is invoked with the parameters expectedParameters.
    Let’s illustrate this with an example. Suppose we want to test a service class which uses a DAO in order to create a new customer.
    If we want our test to be a real unit test, we should isolate our service class by “mocking” the DAO object.
    In this scenario, our only possible verification strategy seems to be checking that the DAO’s creation method is invoked with the right parameters. Let’s see how:

    	[...]
    	// Creating the test double
    	CustomerDao customerDaoDouble = Mockito.mock(CustomerDao.class);
    	
    	// Installing the test double in place of the real customer DAO
    	customerServiceUnderTest.setCustomerDao(customerDaoDouble);
    	
    	// Call the service's creation method
    	customerServiceUnderTest.create(CUSTOMER_CODE, CUSTOMER_NAME);
    	
    	// Verification phase
    	Mockito.verify(customerDaoDouble).save(new CustomerDTO(CUSTOMER_CODE, CUSTOMER_NAME));
    	

    The previous test will fail if save is not invoked as a result of calling create or if it’s invoked with a parameter different [1] from the specified parameter (new CustomerDTO…).

Summary

The technique of testing with mock objects is often misunderstood.
A common mistake is forgetting to install the test double in place of the real object. In this scenario the test double remains unused and it’s the real object that gets called.
I’ve also seen tests exercising the mocked methods instead of the methods in the class under test. Since the implementation of the mocked methods is trivial, such tests are of no value.
But succeeding is really easy if you follow these steps in your use of Mockito:

  1. Create the test double (or mock object, stretching a little bit the meaning).
  2. Set up the double’s behaviour. Optional step, usually done when the methods return some value.
  3. Replace the real object with its corresponding test double.
  4. Exercise the software under test.
  5. Verify. It can be a traditional JUnit/Hamcrest assertion, a Mockito verification, or even a combination of both.

Lastly, some subjects have only been briefly addressed. I expect to elaborate a little bit on them in forthcoming articles. Stay tuned!

1. different according to equals, more on this in forthcoming articles

Advertisements

2 thoughts on “Unit Testing with Mock Objects

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s