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
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:
- 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.
The purpose of tests is to verify the correctness of our software. There are two ways we can do that:
- 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.
- 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:
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  from the specified parameter (new CustomerDTO…).
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:
- Create the test double (or mock object, stretching a little bit the meaning).
- Set up the double’s behaviour. Optional step, usually done when the methods return some value.
- Replace the real object with its corresponding test double.
- Exercise the software under test.
- 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!