Writing test cases is an integral part of software development. Many popular frameworks like Junit can help us with this. However, code development happens incrementally, so not all parts of the system are available for testing at once. For example, let's say that you've written a service that calls two other services and merges their results. Yet those two services are not yet available. How can you test the code you've written?
This is where mocking comes in. One solution is to create dummy classes that implement those services and inject them into your service for testing. You'll have to hard-code the data into those dummy classes. But then the dummy methods will only return one set of data, and you usually want to test many scenarios. How do you achieve this?
Mocking Frameworks
Mocking frameworks like EasyMock, Mockito, etc., provide functionality to create mock objects that will return mock data. You can have different test cases using different sets of data as needed.
Another requirement for mocking may be for test performance and portability. For example, a data access object (DAO) may use a database that's only available on the company network or is expensive to query. Or you may not want to create and maintain a test database. One of the problems with a test database is that if multiple testers create their own data sets, they may break existing test cases. For example, if a findAll() was earlier returning ten records and you tested for that, adding or deleting data will now break that test case. You could get around this by marking which data is for which test case. Using a mocking framework can address these problems.
While you may use mocking for unit testing, it's still important to have integration testing to check that the actual deployment of services works too.
In this article, we will explore how to use the EasyMock framework for mocking.
The Class to Test
Let's say you want to test a CustomerService class, which itself depends on two other classes: CustomerDao, which interacts with the database to add, delete, fetch or update customers, and CreditCheckService, which provides a credit rating for a customer.
Here's the code for CustomerService:
Code
Notice how we are injecting the dependencies of the service via its constructor. This makes it easier to use mock objects for the dependencies instead of the actual ones.
The Test Case
Let's say you want to write a Junit test case to test CustomerService. And let's say that you haven't implemented CreditCheckService yet and that you don't want to use a test database for the DAO. You can mock CustomerDao and CreditCheckService for testing.
One important thing to understand is what and when to mock. Use mocks only in test cases. Since you want to test CustomerService, you should not mock that. Only mock those classes that you don't want to test in that test case. These are usually dependencies of the class that you're going to test.
Steps for writing a test case
The steps for writing a test case with mocking are below:
- Create a mock object. In EasyMock, provide a class as an argument to the mock() method. It can be either an interface or a class. Mocking is done by implementing or extending the class to be mocked. See Objenesis for details on how EasyMock creates mock instances. Mock instances have no state to start with, and the methods usually return nulls. Mockito provides an additional facility to mock a class instance, preserving its state.
- Inject the mock object into the instance of the class you want to test. You can also do this after step 3, i.e., after specifying the mock data.
- Specify which methods are expected to be called on the mocked objects and what values they should return if any. We typically use the expect() and andReturn() methods to do this. You could also make a method throw an exception with andThrow().
- Set up the mock object with data and behavior specified in the above step. We do this using the replay() method. The name is a bit confusing; maybe something like buildMockData() would have been better.
- Perform operations on the instance of the class you want to test, which is internally using the mocked objects.
- Verify that the expected methods (specified in step 3) on the mocked objects were called by the test case. This is optional. Use the verify() method for this.
- Verify that the class you want to test is working correctly using the usual assertions. This was the main goal in writing the test case, and mocking the dependencies was just one part of it.
Below is how what a test case might look like.
Code
Explanation
The code and comments follow the order of the steps outlined earlier. We are mocking CustomerDao and CreditCheckService. We expect CustomerDao's findAll() method to be called with a null argument for filters, and when it is called, we want to return the customers in the custList variable. This list has three customer IDs: 1,2,3, which the CustomerService will pass to CreditCheckService. So, we expect that CreditCheckService's getCreditRating() method will be called for these IDs, and we want to return a different rating for each one. With replay(), we are saying that we are finished specifying the mock behavior and now want to build it into the mock objects.
Now you're ready to run the actual test case for CustomerService, which tests whether the getCustomersWithGoodCreditRating() method works correctly. It should only return those customers with a good credit rating. In this case, that's the customer with id=1 and rating=10. The asserts in the end verify that.
Let's try some changes to the above code to understand it better.
Not providing all mock data
Comment out one of the mock credit ratings:
This should lead to an exception:
Here, we did not provide the mock credit rating for customer id=1. However, a customer with id=1 is present in the list of customers, hence the code will invoke getCreditRating (1). The mock will only contain the data for the inputs specified with expect(). For unspecified inputs, EasyMock does not know what to return, so it throws an exception. If we were to also comment out the customer ID 1 from the customers list, we would not get this error because getCreditRating (1) would not be called.
EasyMock provides a NiceMock implementation that allows methods to be called without a corresponding expect(), in which case it will return appropriate default values like 0, null, etc. We can use the niceMock() method to create the nice mock.
Argument matchers
In the example above, we had to specify the mock credit ratings three times since we wanted to specify a different return value for each. If we had wanted the same return value for all customer IDs, we could have written just one expect, like this:
With the anyLong() argument matcher, we are saying that we can pass any Long number as the customer ID. Note the times(3). It specifies how many times we expect to call the method, the default being 1. If we call any method more than once, we'll need to add a times clause. In our case, it's going to be called three times as we have three customers on the list. The default mock implementation will check that the number of times a method is called matches the expected times() and will throw an error if it doesn't. If you don't want to check how many times a method is called, you have two options:
- Use anyTimes() instead of times().
- Use niceMock() instead of mock(). The NiceMock implementation is lenient in this regard too. (The StrictMock, on the other hand, has even more checks, such as the order in which we have called the methods.)
There are many other argument matcher methods available, such as eq, not, anyXXX, aryEq, isA, same, isNull, and, or, not, lt, gt, matches. You can even write custom argument matchers. See the user guide for details.
Returning dynamic values
You saw how you can return fixed values with andReturn(). What if you want to compute the value dynamically? You can use the andAnswer() or andDelegateTo() methods instead.
In the above case, getCurrentArguments() is the list of arguments passed to the getCreditRating() method. So, we are just returning the value we received as input.
Different return values per invocation
It's possible to set up a method to return different values for the same inputs.
The mocked getCreditRating() in the above case will return 4 for the first five times we call it, then throw an exception for the next five times, and then return 10 for the five times after that.
Mocking without expect
After you create a mock object, it will be in a recording state until you call replay(). So, calling a method on the mock object also works like expect(), but you cannot specify argument matchers or return types. It will work only for methods returning void because for methods returning something, EasyMock also expects an andReturn clause, or else it will throw an exception. The two lines below will each do the same thing: add an expectation that the someVoidMethod will be called with an argument of 3.
You can use the expectLastCall() method to define expectations on the most recent call you made on the mock object.
Partial mocks
Mocking a class will mock all its public methods. But what if you want to mock only certain methods of a class and retain the original functionality of the others? Let's say, for example, that the CreditCheckService had a getProvider() method, which you did not want to mock. In such cases, you can create a partial mock that mocks only some methods.
Now EasyMock will mock only the getCreditRating method.
Resetting a mock
If you want to reuse the same mock instance but with a different set of data, it's possible using the reset() method. A reset() can also convert to another type of mock, such as resetToNice(), resetToDefault() or resetToStrict().
Annotations
You can create a mock using the @Mock annotation. You need to annotate the test class with the @RunWith(EasyMockRunner.class).
Limitations and Caveats
- You cannot mock final classes and methods as mocking requires extending the original class and overriding its methods, which is not allowed for final classes and methods.
- You can mock only public, non-static methods. Also, EasyMock overrides the equals, hashCode, toString, and finalize methods of a mocked object. The original versions are not available unless you use a partial mock.
The PowerMock framework extends EasyMock further and can overcome some of these limitations.
Conclusion
You have seen why mocking is necessary and the basic usage of the EasyMock framework. EasyMock makes it easy to set up mock objects and have them return mock data or throw exceptions. Features like partial mocking, argument matchers, and dynamic returns add to its power. Manually writing and managing test cases on a large project can be cumbersome, but there are no-code testing tools like Waldo that make it easier.
Automated E2E tests for your mobile app
Get true E2E testing in minutes, not months.