Mock Objects in Unit Tests
The use of mock objects is a widely employed unit testing strategy. It shields external and unnecessary factors from testing and helps developers focus on a specific function to be tested.
java.lang.reflect.Proxy, which can create dynamic proxy classes/objects according to given interfaces. But it has an inherent limitation from its use of
Proxy: it can create mock objects only for interfaces.
Mocquer is a similar mock tool, but one that extends the functionality of EasyMock to support mock object creation for classes as well as interfaces.
Introduction to Mocquer
Mocquer is based on the Dunamis project, which is used to generate dynamic delegation classes/objects for specific interfaces/classes. For convenience, it follows the class and method naming conventions of EasyMock, but uses a different approach internally.
MockControl is the main class in the Mocquer project. It is used to control the the mock object life cycle and behavior definition. There are four kinds methods in this class.
- Life Cycle Control Methods
The mock object has three states in its life cycle: preparing, working, and checking. Figure 1 shows the mock object life cycle.
Initially, the mock object is in the preparing state. The mock object's behavior can be defined in this state.
Figure 1. Mock object life cycle
replay()changes the mock object's state to the working state. All method invocations on the mock object in this state will follow the behavior defined in the preparing state. After
verify()is called, the mock object is in the checking state.
MockControlwill compare the mock object's predefined behavior and actual behavior to see whether they match. The match rule depends on which kind of
MockControlis used; this will be explained in a moment. The developer can use
replay()to reuse the predefined behavior if needed. Call
reset(), in any state, to clear the history state and change to the initial preparing state.
- Factory Methods
Mocquer provides three kinds of
Strict. The developer can choose an appropriate
MockControlin his or her test case, according to what is to be tested (the test point) and how the test will be carried out (the test strategy). The
MockControlis the loosest. It does not care about the order of method invocation on the mock object, or about unexpected method invocations, which just return a default value (that depends on the method's return value). The
MockControlis stricter than the
MockControl, as an unexpected method invocation on the mock object will lead to an
MockControlis, naturally, the strictest. If the order of method invocation on the mock object in the working state is different than that in the preparing state, an
AssertionFailedErrorwill be thrown. The table below shows the differences between these three kinds of
Unexpected Order Doesn't care Doesn't care
Unexpected Method Default value
There are two versions for each factory method.
If the class to be mocked is an interface or it has a public/protected default constructor, the first version is enough. Otherwise, the second version factory method is used to specify the signature and provide arguments to the desired constructor. For example, assuming
ClassWithNoDefaultConstructoris a class without a default constructor:
MockControlcan be obtained through:
- Mock object getter method
MockControlcontains a reference to the generated mock object. The developer can use this method to get the mock object and cast it to the real type.
- Behavior definition methods
MockControlallows the developer to define the mock object's behavior per each method invocation on it. When in the preparing state, the developer can call one of the mock object's methods first to specify which method invocation's behavior is to be defined. Then, the developer can use one of the behavior definition methods to specify the behavior. For example, take the following
The behavior of the mock object can be defined as in the following:
Most of the more than 50 methods in
MockControlare behavior definition methods. They can be grouped into following categories.
These methods are used to specify that the last method invocation should return a value as the parameter. There are seven versions of
setReturnValue(), each of which takes a primitive type as its parameter, such as
setReturnValue(Object obj)is used for a method that takes an object instead of a primitive. If the given value does not match the method's return type, an
AssertionFailedErrorwill be thrown.
It is also possible to add the number of expected invocations into the behavior definition. This is called the invocation times limitation.
The code segment above specifies that the method invocation,
bar(10), can only occur once. How about providing a range?
bar(10)is limited to be called at least once and at most, three times. More appealingly, a
Rangecan be given to specify the limitation.
Range.ONE_OR_MOREis a pre-defined
Rangeinstance, which means the method should be called at least once. If there is no invocation-count limitation specified in
setReturnValue(), such as
setReturnValue("Hello"), it will use
Range.ONE_OR_MOREas its default invocation-count limitation. There are another two predefined
Range.ONE(exactly once) and
Range.ZERO_OR_MORE(there's no limit on how many times you can call it).
There is also a special set return value method:
setDefaultReturnValue(). It defines the return value of the method invocation despite the method parameter values. The invocation times limitation is
Range.ONE_OR_MORE. This is known as the method parameter values insensitive feature.
setThrowable(Throwable throwable)is used to define the method invocation's exception throwing behavior. If the given throwable does not match the exception declaration of the method, an
AssertionFailedErrorwill be thrown. The invocation times limitation and method parameter values insensitive features can also be applied.
setVoidCallable()is used for a method that has a
voidreturn type. The invocation times limitation and method parameter values insensitive features can also be applied.
In the working state, the
MockControlwill search the predefined behavior when any method invocation has happened on the mock object. There are three factors in the search criteria: method signature, parameter value, and invocation times limitation. The first and third factors are fixed. The second factor can be skipped by the parameter values insensitive feature described above. More flexibly, it is also possible to customize the parameter value match rule.
setMatcher()can be used in the preparing state with a customized
The only method in
matches(), takes two arguments. One is the expected parameter values array (null, if the parameter values insensitive feature applied). The other is the actual parameter values array. A true return value means that the parameter values match.
There are three predefined
MockControl.ALWAYS_MATCHERalways returns true when matching, no matter what parameter values are given.
equals()on each element in the parameter value array.
MockControl.ARRAY_MATCHERis almost the same as
MockControl.EQUALS_MATCHER, except that it calls
equals()when the element in the parameter value array is an array type. Of course, the developer can implement his or her own
A side effect of a customized
ArgumentsMatcheris that it defines the method invocation's out parameter value.
ArgumentsMatcherinstance. If no specific
ArgumentsMatcheris given, the default
ArgumentsMatcherwill be used. This method should be called before any method invocation behavior definition. Otherwise, an
AssertionFailedErrorwill be thrown.
setDefaultMatcher()is not used,
MockControl.ARRAY_MATCHERis the system default