In the JMockit library, the Expectations API provides rich support for the use of mocking in automated developer tests. When mocking is used, a test focuses on the behavior of the code under test, as expressed through its interactions with other types it depends upon. Mocking is typically used in the construction of isolated unit tests, where a unit under test is exercised in isolation from the implementation of other units it depends on. Typically, a unit of behavior is a single class, but it's also fine to consider a whole set of strongly-related classes as a single unit for the purposes of unit testing (as is usually the case when we have a central public class with one or more helper classes, possibly package-private); in general, individual methods should not be regarded as separate units on their own.
Strict unit testing, however, is not a recommended approach; one should not attempt to mock every single dependency. Mocking is best used in moderation; whenever possible, favor integration tests over isolated unit tests. This said, mocking is occasionally also useful in the creation of integration tests, when some particular dependency cannot have its real implementation easily used, or when attempting to create tests for corner cases where a well-placed mocked interaction can greatly facilitate the test.
Mocking provides a mechanism for isolating a class to be tested from (some of) its dependencies.
We specify which particular dependencies are to be mocked by declaring suitable mock fields and/or mock parameters in a
test class; mock fields are declared as annotated instance fields of the test class, while mock parameters are declared as annotated
parameters of a test method.
The type of the mock field or parameter can be any kind of reference type: an interface
, a class
(including abstract
and final
ones), an annotation, or an enum
.
The following example test skeleton (using Java 8 and JUnit 5) serves as a basic illustration for the declaration of mock fields and mock parameters, as well as the way in which they are typically used in test code.
@Mocked Dependency mockInstance; // holds a mocked instance automatically created for use in each test
@Test
void doBusinessOperationXyz(@Mocked AnotherDependency anotherMock) {
...
new Expectations() {{ // an "expectation block"
...
// Record an expectation, with a given value to be returned:
mockInstance.mockedMethod(...); result = 123;
...
}};
...
// Call the code under test.
...
new Verifications() {{ // a "verification block"
// Verifies an expected invocation:
anotherMock.save(any); times = 1;
}};
...
}
For a mock parameter declared in a test method, an instance of the declared type will be automatically created by JMockit and passed by
the JUnit/TestNG test runner when it executes the test method; therefore, the parameter value will never be null
.
For a mock field, an instance of the declared type will be automatically created and assigned to the field (provided it's not
final
).
There are three different mocking annotations we can use when declaring mock fields and parameters:
@Mocked
, which will mock all methods and constructors on all existing and future instances of a
mocked class (for the duration of the tests using it); @Injectable
, which constrains mocking to the
instance methods of a single mocked instance; and @Capturing
, which extends mocking to the classes
implementing a mocked interface, or the subclasses extending a mocked class.
The mocked instances created by JMockit can be used normally in test code (for the recording and verification of expectations), and/or
passed to the code under test. Or they may simply go unused.
Differently from other mocking APIs, these mocked objects don't have to be the ones used by the code under test when it calls
instance methods on its dependencies.
When using @Mocked
or @Capturing
(but not when using
@Injectable
), JMockit does not care on which particular object a mocked instance method is called.
This allows the transparent mocking of instances created directly inside code under test, when said code invokes constructors on brand
new instances using the new
operator; the classes instantiated must be covered by mocked types declared in test code, that's
all.
An expectation represents a set of invocations to a specific mocked method/constructor that is relevant for a given test. An expectation may cover multiple different invocations to the same method or constructor, but it doesn't have to cover all such invocations that occur during the execution of the test. Whether a particular invocation matches a given expectation or not will depend not only on the method/constructor signature but also on runtime aspects such as the instance on which the method is invoked, argument values, and/or the number of invocations already matched. Therefore, several types of matching constraints can (optionally) be specified for a given expectation.
When we have one or more invocation parameters involved, an exact argument value may be specified for each parameter.
For example, the value "test string"
could be specified for a String
parameter, causing the expectation to
match only those invocations with this exact value in the corresponding parameter.
As we will see later, instead of specifying exact argument values, we can specify more relaxed constraints which will match whole sets of
different argument values.
The example below shows an expectation for Dependency#someMethod(int, String)
, which will match an invocation to this method
with the exact argument values as specified.
Notice that the expectation itself is specified through an isolated invocation to the mocked method.
There are no special API methods involved, as is common in other mocking APIs.
This invocation, however, does not count as one of the "real" invocations we are interested in testing.
It's only there so that the expectation can be specified.
@Test
void doBusinessOperationXyz(@Mocked Dependency mockInstance) {
...
new Expectations() {{
...
// An expectation for an instance method:
mockInstance.someMethod(1, "test"); result = "mocked";
...
}};
// A call to code under test occurs here, leading to mock invocations
// that may or may not match specified expectations.
}
We will see more about expectations later, after we understand the differences between recording, replaying, and verifying invocations.
Any developer test can be divided in at least three separate execution phases. The phases execute sequentially, one at a time, as demonstrated below.
@Test
void someTestMethod() {
// 1. Preparation: whatever is required before the code under test can be exercised.
...
// 2. The code under test is exercised, usually by calling a public method.
...
// 3. Verification: whatever needs to be checked to make sure the code exercised by
// the test did its job.
...
}
First, we have a preparation phase, where objects and data items needed for the test are created or obtained from somewhere else. Then, code under test is exercised. Finally, the results from exercising the tested code are compared with the expected results.
This model of three phases is also known as the Arrange, Act, Assert syntax, or "AAA" for short. Different words, but the meaning is the same.
In the context of behavior-based testing with mocked types (and their mocked instances), we can identify the following alternative phases, which are directly related to the three previously described conventional testing phases:
Behavior-based tests written with JMockit will typically fit the following templates:
import mockit.*;
... other imports ...
class SomeTest {
// Zero or more "mock fields" common to all test methods in the class:
@Mocked Collaborator mockCollaborator;
@Mocked AnotherDependency anotherDependency;
...
@Test
void testWithRecordAndReplayOnly(mock parameters) {
// Preparation code not specific to JMockit, if any.
new Expectations() {{ // an "expectation block"
// One or more invocations to mocked types, causing expectations to be recorded.
// Invocations to non-mocked types are also allowed anywhere inside this block
// (though not recommended).
}};
// Code under test is exercised.
// Verification code (JUnit/TestNG assertions), if any.
}
@Test
void testWithReplayAndVerifyOnly(mock parameters) {
// Preparation code not specific to JMockit, if any.
// Code under test is exercised.
new Verifications() {{ // a "verification block"
// One or more invocations to mocked types, causing expectations to be verified.
// Invocations to non-mocked types are also allowed anywhere inside this block
// (though not recommended).
}};
// Additional verification code, if any, either here or before the verification block.
}
@Test
void testWithBothRecordAndVerify(mock parameters) {
// Preparation code not specific to JMockit, if any.
new Expectations() {{
// One or more invocations to mocked types, causing expectations to be recorded.
}};
// Code under test is exercised.
new VerificationsInOrder() {{ // an ordered verification block
// One or more invocations to mocked types, causing expectations to be verified
// in the specified order.
}};
// Additional verification code, if any, either here or before the verification block.
}
}
There are other variations to the above templates, but the essence is that the expectation blocks belong to the record phase and come before the code under test is exercised, while the verification blocks belong to the verify phase. A test method can contain any number of expectation blocks, including none. The same is true for verification blocks.
A non-final
instance field annotated as @Tested
in the test class will be considered for
automatic instantiation and injection, just before the execution of a test method.
If at this time the field still holds the null
reference, an instance will be created using a suitable constructor of the
tested class, while making sure its internal dependencies get properly injected (when applicable).
In order to inject mocked instances into the tested object, the test class must also contain one or more mock fields or mock
parameters declared to be @Injectable
.
Mock fields/parameters annotated only with @Mocked
or @Capturing
are
not considered for injection.
On the other hand, not all injectable fields/parameters need to have mockable types; they can also have primitive or
array types.
The following example test class will demonstrate.
class SomeTest {
@Tested CodeUnderTest tested;
@Injectable Dependency dep1;
@Injectable AnotherDependency dep2;
@Injectable int someIntegralProperty = 123;
@Test
void someTestMethod(@Injectable("true") boolean flag, @Injectable("Mary") String name) {
// Record expectations on mocked types, if needed.
tested.exerciseCodeUnderTest();
// Verify expectations on mocked types, if required.
}
}
Note that a non-mockable injectable field/parameter must have a value explicitly specified for it, otherwise the default value would be
used.
In the case of an injectable field, the value can simply be assigned to the field.
Alternatively, it can be provided in the "value
" attribute of @Injectable
, which is the
only way to specify the value in the case of an injectable test method parameter.
Two forms of injection are supported: constructor injection and field injection. In the first case, the tested class must have a constructor which can be satisfied by the injectables and/or tested values made available in the test class. Note that for a given test, the set of available injectable/tested values consists of the set of injectable/tested fields declared as instances fields of the test class plus the set of injectable/tested parameters declared in the test method; therefore, different tests in the same test class can provide different sets of values to be injected into the same tested object.
Once the tested class is initialized with the chosen constructor, its remaining uninitialized non-final
instance fields are
considered for injection.
For each such field to be injected, an injectable/tested field of the same type is searched in the test class.
If only one is found, its current value is read and then stored in the injected field.
If there is more than one, the injected field name is used to select between the injectable/tested fields of same type.
For a given method with non-void
return type, a return value can be recorded with an assignment to the
result
field.
When the method gets called in the replay phase, the specified return value will be returned to the caller.
The assignment to result
should appear right after the invocation that identifies the recorded expectation,
inside an expectation block.
If the test instead needs an exception or error to be thrown when the method is invoked, then the result
field
can still be used: simply assign the desired throwable instance to it.
Note that the recording of exceptions/errors to be thrown is applicable to mocked methods (of any return type) as well as to mocked
constructors.
Multiple consecutive values to return can be recorded for an expectation, by calling the returns(v1, v2, ...)
method.
Alternatively, the same can be achieved by assigning the result
field with a list or array
containing the consecutive values.
The following example test records both types of results for the methods of a mocked DependencyAbc
class, to
be used when they are invoked from ClassUnderTest
.
Lets say the implementation of the class under test goes like this:
public class ClassUnderTest {
private final DependencyAbc abc = new DependencyAbc();
public void doSomething() {
(1) int n = abc.intReturningMethod();
for (int i = 0; i < n; i++) {
String s;
try {
(2) s = abc.stringReturningMethod();
}
catch (SomeCheckedException e) {
// somehow handle the exception
}
// do some other stuff
}
}
}
A possible test for the doSomething()
method could exercise the case where SomeCheckedException
gets thrown, after an arbitrary number of successful iterations.
Assuming that we want to record a set of expectations for the interaction between these two classes, we might write the test below.
@Tested ClassUnderTest cut;
@Test
void doSomethingHandlesSomeCheckedException(@Mocked DependencyAbc abc) throws Exception {
new Expectations() {{
(1) abc.intReturningMethod(); result = 3;
(2) abc.stringReturningMethod();
returns("str1", "str2");
result = new SomeCheckedException();
}};
cut.doSomething();
}
This test records two expectations.
The first one specifies that intReturningMethod()
will return 3
when called.
The second specifies a sequence of three consecutive results for stringReturningMethod()
, where the last result happens to
be an instance of the desired exception, allowing the test to achieve its goal.
When a mocked method/constructor has one or more parameters, a recorded/verified expectation like doSomething(1, "s", true);
will only match an invocation in the replay phase if it has equal argument values.
For arguments that are regular objects (not primitives or arrays), the equals(Object)
method is used for equality checking.
For parameters of array type, equality checking extends to individual elements; therefore, two different array instances having the same
length in each dimension and equal corresponding elements are considered equal.
To allow a recorded or verified invocation to match a whole set of replayed invocations with different argument values, we can specify
flexible argument matching constraints instead of actual argument values.
This is done by using anyXyz
fields and/or withXyz(...)
methods, from inside expectation or
verification blocks.
The most commonly used argument matching constraints are those that simply match invocations with any value for a given
parameter (of the proper parameter type).
For that we have a whole set of special argument matching fields, one for each primitive type (and the corresponding wrapper
class), one for strings, and a "universal" one of type Object
.
The test below demonstrates some uses.
@Tested CodeUnderTest cut;
@Test
void someTestMethod(@Mocked DependencyAbc abc) {
DataItem item = new DataItem(...);
new Expectations() {{
// Will match "voidMethod(String, List)" invocations where the first argument is
// any string and the second any list.
abc.voidMethod(anyString, (List<?>) any);
}};
cut.doSomething(item);
new Verifications() {{
// Matches invocations to the specified method with any value of type long or Long.
abc.anotherVoidMethod(anyLong);
}};
}
Uses of "any
" fields must appear at the actual argument positions in the invocation statement, never before.
You can still have regular argument values for other parameters in the same invocation, though.
For more details, consult the API documentation from your Java IDE.
When recording or verifying an expectation, calls to the withXyz(...)
methods can occur for any subset of the arguments
passed in the invocation.
They can be freely mixed with regular argument-passing (using literal values, local variables, etc.).
The only requirement is that such calls appear inside the recorded/verified invocation statement, rather than before it.
It's not possible, for example, to first assign the result of a call to withNotEqual(val)
to a local variable and then use
the variable in the invocation statement.
An example test using some of the "with" methods is shown below.
@Test
void someTestMethod(@Mocked DependencyAbc abc) {
DataItem item = new DataItem(...);
new Expectations() {{
// Will match "voidMethod(String, List)" invocations with the first argument
// equal to "str" and the second not null.
abc.voidMethod("str", (List<?>) withNotNull());
// Will match invocations to DependencyAbc#stringReturningMethod(DataItem, String)
// with the first argument pointing to "item" and the second one containing "xyz".
abc.stringReturningMethod(withSameInstance(item), withSubstring("xyz"));
}};
cut.doSomething(item);
new Verifications() {{
// Matches invocations to the specified method with any long-valued argument.
abc.anotherVoidMethod(withAny(1L));
}};
}
There are more "with" methods than shown above. Use your IDE's code completion, and consult the API documentation for more details.
Besides the several predefined argument matching constraints available in the API, JMockit allows the user to provide custom constraints,
through the with(Delegate)
method.
The number of invocations expected and/or allowed to match a given expectation can be specified through invocation count
constraints.
There are three special fields just for that: times
, minTimes
, and
maxTimes
.
They can be used either when recording or when verifying expectations.
In either case, the method or constructor associated with the expectation will be constrained to receive a number of invocations that
falls in the specified range.
Any invocations less or more than the expected lower or upper limit, respectively, and the test execution will automatically fail.
Lets see some example tests.
@Tested CodeUnderTest cut;
@Test
void someTestMethod(@Mocked DependencyAbc abc) {
new Expectations() {{
// By default, at least one invocation is expected, i.e. "minTimes = 1":
new DependencyAbc();
// At least two invocations are expected:
abc.voidMethod(); minTimes = 2;
// 1 to 5 invocations are expected:
abc.stringReturningMethod(); minTimes = 1; maxTimes = 5;
}};
cut.doSomething();
}
@Test
void someOtherTestMethod(@Mocked DependencyAbc abc) {
cut.doSomething();
new Verifications() {{
// Verifies that zero or one invocations occurred, with the specified argument value:
abc.anotherVoidMethod(3); maxTimes = 1;
// Verifies the occurrence of at least one invocation with the specified arguments:
DependencyAbc.someStaticMethod("test", false); // "minTimes = 1" is implied
}};
}
Unlike the result
field, each of these three fields can be specified at most once for a given expectation.
Any non-negative integer value is valid for any of the invocation count constraints.
If times = 0
or maxTimes = 0
is specified, the first
invocation matching the expectation to occur during replay (if any) will cause the test to fail.
Besides specifying invocation count constraints on recorded expectations, we can also verify matching invocations explicitly in verification blocks, after the call to the code under test.
Inside a "new Verifications() {...}
" block we can use the same API that's available in a
"new Expectations() {...}
" block, with the exception of methods and fields used to record return values
and thrown exceptions.
That is, we can freely use the anyXyz
fields, the withXyz(...)
argument matching methods, and the
times
, minTimes
, and maxTimes
invocation count constraint fields.
An example test follows.
@Test
void verifyInvocationsExplicitlyAtEndOfTest(@Mocked Dependency mock) {
// Nothing recorded here, though it could be.
// Inside tested code:
Dependency dependency = new Dependency();
dependency.doSomething(123, true, "abc-xyz");
// Verifies that Dependency#doSomething(int, boolean, String) was called at least once,
// with arguments that obey the specified constraints:
new Verifications() {{ mock.doSomething(anyInt, true, withPrefix("abc")); }};
}
Note that, by default, a verification checks that at least one matching invocation occurred during replay.
When we need to verify an exact number of invocations (including 1
), the
times = n
constraint must be specified.
Regular verification blocks created with the Verifications
class are unordered.
The actual relative order in which aMethod()
and anotherMethod()
were called during the replay phase is not
verified, but only that each method was executed at least once.
If you want to verify the relative order of invocations, then a "new VerificationsInOrder() {...}
" block must be used
instead.
Inside this block, simply write invocations to one or more mocked types in the order they are expected to have occurred.
@Test
void verifyingExpectationsInOrder(@Mocked DependencyAbc abc) {
// Somewhere inside the tested code:
abc.aMethod();
abc.doSomething("blah", 123);
abc.anotherMethod(5);
...
new VerificationsInOrder() {{
// The order of these invocations must be the same as the order
// of occurrence during replay of the matching invocations.
abc.aMethod();
abc.anotherMethod(anyInt);
}};
}
Note that the call abc.doSomething(...)
was not verified in the test, so it could have occurred at any time (or
not at all).
Sometimes it may be desired to have all invocations to the mocked types/instances involved in a test verified.
In such cases, a "new FullVerifications() {...}
" block will make sure that no invocations are left unverified.
@Test
void verifyAllInvocations(@Mocked Dependency mock) {
// Code under test included here for easy reference:
mock.setSomething(123);
mock.setSomethingElse("anotherValue");
mock.setSomething(45);
mock.save();
new FullVerifications() {{
mock.setSomething(anyInt); // verifies two actual invocations
mock.setSomethingElse(anyString);
mock.save(); // if this verification (or any other above) is removed the test will fail
}};
}
To decide the result of a recorded expectation based on the arguments it receives at replay time, we can use a
Delegate
object, as exemplified below.
@Tested CodeUnderTest cut;
@Test
void delegatingInvocationsToACustomDelegate(@Mocked DependencyAbc anyAbc) {
new Expectations() {{
anyAbc.intReturningMethod(anyInt, anyString);
result = new Delegate() {
int aDelegateMethod(int i, String s) {
return i == 1 ? i : s.length();
}
};
}};
// Calls to "intReturningMethod(int, String)" will execute the delegate method above.
cut.doSomething();
}
The Delegate
interface is empty, being used simply to tell JMockit that actual invocations at replay time
should be delegated to the "delegate" method in the assigned object.
This method can have any name, provided it is the only non-private
method in the delegate object.
As for the parameters of the delegate method, they should either match the parameters of the recorded method, or there should be none.
In any case, the delegate method is allowed to have an additional parameter of type Invocation
as its first
parameter.
The Invocation
object received during replay will provide access to the invoked instance and the actual
invocation arguments, along with other abilities.
The return type of a delegate method doesn't have to be the same as the recorded method, although it should be compatible in order to
avoid a ClassCastException
later.
Constructors can also be handled through delegate methods. The following example test shows a constructor invocation being delegated to a method which conditionally throws an exception.
@Test
void delegatingConstructorInvocations(@Mocked Collaborator anyCollaboratorInstance) {
new Expectations() {{
new Collaborator(anyInt);
result = new Delegate() {
void delegate(int i) { if (i < 1) throw new IllegalArgumentException(); }
};
}};
// The first instantiation using "Collaborator(int)" will execute the delegate above.
new Collaborator(4);
}
Invocation arguments can be captured for later verification through a set of special "withCapture(...)
" methods.
There are three different cases, each with its own specific capturing method:
T withCapture()
;T withCapture(List<T>)
;List<T> withCapture(T)
.
To capture arguments from a single invocation to a mocked method or constructor, we use withCapture()
,
as the following example test demonstrates.
@Test
void capturingArgumentsFromSingleInvocation(@Mocked Collaborator mock) {
// Inside tested code:
...
new Collaborator().doSomething(0.5, new int[2], "test");
// Back in test code:
new Verifications() {{
double d;
String s;
mock.doSomething(d = withCapture(), null, s = withCapture());
assertTrue(d > 0.0);
assertTrue(s.length() > 1);
}};
}
The withCapture()
method can only be used in verification blocks.
Typically, we use it when a single matching invocation is expected to occur; if more than one such invocation occurs, however,
the last one to occur overwrites the values captured by previous ones.
It is particularly useful with parameters of a complex type (think a JPA @Entity
), which may contain
several items whose values need to be checked.
If multiple invocations to a mocked method or constructor are expected, and we want to capture values for all of them, then the
withCapture(List)
method should be used instead, as in the example below.
@Test
void capturingArgumentsFromMultipleInvocations(@Mocked Collaborator mock) {
// Inside tested code:
mock.doSomething(dataObject1);
mock.doSomething(dataObject2);
...
// Back in test code:
new Verifications() {{
List<DataObject> dataObjects = new ArrayList<>();
mock.doSomething(withCapture(dataObjects));
assertEquals(2, dataObjects.size());
DataObject data1 = dataObjects.get(0);
DataObject data2 = dataObjects.get(1);
// Perform arbitrary assertions on data1 and data2.
}};
}
Differently from withCapture()
, the withCapture(List)
overload can also be used in expectation recording
blocks.
Finally, we can capture the new instances of a mocked class that got created during the test.
@Test
void capturingNewInstances(@Mocked Person mockedPerson) {
// From the code under test:
dao.create(new Person("Paul", 10));
dao.create(new Person("Mary", 15));
dao.create(new Person("Joe", 20));
...
// Back in test code:
new Verifications() {{
// Captures the new instances created with a specific constructor.
List<Person> personsInstantiated = withCapture(new Person(anyString, anyInt));
// Now captures the instances of the same type passed to a method.
List<Person> personsCreated = new ArrayList<>();
dao.create(withCapture(personsCreated));
// Finally, verifies both lists are the same.
assertEquals(personsInstantiated, personsCreated);
}};
}
When using complex APIs where functionality is distributed through many different objects, it is not uncommon to see chained invocations
of the form obj1.getObj2(...).getYetAnotherObj().doSomething(...)
.
In such cases it may be necessary to mock all objects/classes in the chain, starting with obj1
.
All three mocking annotations provide this ability.
The following test shows a basic example, using the java.net
and java.nio
APIs.
@Test
void recordAndVerifyExpectationsOnCascadedMocks(
@Mocked Socket anySocket, // will match any new Socket object created during the test
@Mocked SocketChannel cascadedChannel // will match cascaded instances
) throws Exception {
new Expectations() {{
// Calls to Socket#getChannel() will automatically return a cascaded SocketChannel;
// such an instance will be the same as the second mock parameter, allowing us to
// use it for expectations that will match all cascaded channel instances:
cascadedChannel.isConnected(); result = false;
}};
// Inside production code:
Socket sk = new Socket(); // mocked as "anySocket"
SocketChannel ch = sk.getChannel(); // mocked as "cascadedChannel"
if (!ch.isConnected()) {
SocketAddress sa = new InetSocketAddress("remoteHost", 123);
ch.connect(sa);
}
InetAddress adr1 = sk.getInetAddress(); // returns a newly created InetAddress instance
InetAddress adr2 = sk.getLocalAddress(); // returns another new instance
...
// Back in test code:
new Verifications() {{ cascadedChannel.connect((SocketAddress) withNotNull()); }};
}
In the test above, calls to eligible methods in the mocked Socket
class will return a cascaded mock
object whenever they occur during the test.
The cascaded mock will allow further cascading, so a null
reference will never be obtained from methods which return object
references (except for non-eligible return types Object
or String
which will return
null
, or collection types which will return a non-mocked empty collection).
Unless there is an available mocked instance from a mock field/parameter (such as cascadedChannel
above), a new
cascaded instance will get created from the first call to each mocked method.
In the example above, the two different methods with the same InetAddress
return type will create and return
different cascaded instances; the same method will always return the same cascaded instance, though.
New cascaded instances are created with @Injectable
semantics, so as to not affect other instances of the
same type that may exist during the test.
Finally, it's worth noting that, if necessary, cascaded instances can be replaced with non-mocked ones, with a different mocked instance,
or not be returned at all; for that, record an expectation which assigns the result
field with the desired
instance to be returned, or with null
if no such instance is desired.
Cascading is quite useful in scenarios where a mocked class contains static
factory methods.
In the following example test, lets say we want to mock the javax.faces.context.FacesContext
class from JSF
(Java EE).
@Test
void postErrorMessageToUIForInvalidInputFields(@Mocked FacesContext jsf) {
// Set up invalid inputs, somehow.
// Code under test which validates input fields from a JSF page, adding
// error messages to the JSF context in case of validation failures.
FacesContext ctx = FacesContext.getCurrentInstance();
if (some input is invalid) {
ctx.addMessage(null, new FacesMessage("Input xyz is invalid: blah blah..."));
}
...
// Test code: verify appropriate error message was added to context.
new Verifications() {{
FacesMessage msg;
jsf.addMessage(null, msg = withCapture());
assertTrue(msg.getSummary().contains("blah blah"));
}};
}
What's interesting in the test above is that we never have to worry about FacesContext.getCurrentInstance()
, as the
"jsf
" mocked instance gets automatically returned.
Another scenario where cascading tends to help is when code under test uses a
"fluent interface", where a "builder" object returns itself from most
of its methods.
So, we end up with a method call chain which produces some final object or state.
In the example test below we mock the java.lang.ProcessBuilder
class.
@Test
void createOSProcessToCopyTempFiles(@Mocked ProcessBuilder pb) throws Exception {
// Code under test creates a new process to execute an OS-specific command.
String cmdLine = "copy /Y *.txt D:\\TEMP";
File wrkDir = new File("C:\\TEMP");
Process copy = new ProcessBuilder().command(cmdLine).directory(wrkDir).inheritIO().start();
int exit = copy.waitFor();
...
// Verify the desired process was created with the correct command.
new Verifications() {{ pb.command(withSubstring("copy")).start(); }};
}
Above, methods command(...)
, directory(...)
, and inheritIO()
configure the process to be created,
while start()
finally creates it.
The mocked process builder object automatically returns itself ("pb
") from these calls, while also returning a new mocked
Process
from the call to start()
.
Previously, we explained that an expectation recorded on a mocked instance, such as "abc.someMethod();
"
would actually match invocations to DependencyAbc#someMethod()
on any instance of the mocked
DependencyAbc
class.
In most cases, tested code uses a single instance of a given dependency, so this won't really matter and can be safely ignored,
whether the mocked instance is passed into the code under test or created inside it.
But what if we need to verify that invocations occur on a specific instance, between several ones that happen to be used in the
code under test?
Also, what if only one or a few instances of the mocked class should actually be mocked, with other instances of the same class
remaining unmocked?
(This second case tends to occur more often when classes from the standard Java libraries, or from other third-party libraries, are
mocked.)
The API provides a mocking annotation, @Injectable
, which will only mock one instance of the
mocked type, leaving others unaffected.
Additionally, we have a couple ways to constrain the matching of expectations to specific @Mocked
instances, while still mocking all instances of the mocked class.
Suppose we need to test code which works with multiple instances of a given class, some of which we want to mock.
If an instance to be mocked can be passed or injected into the code under test, then we can declare an
@Injectable
mock field or mock parameter for it.
This @Injectable
instance will be an "exclusive" mocked instance; any other instance of the same mocked
type, unless obtained from a separate mock field/parameter, will remain as a regular, non-mocked instance.
When using @Injectable
, static
methods and constructors are also excluded from being mocked.
After all, a static
method is not associated with any instance of the class, while a constructor is only associated with a
newly created (and therefore different) instance.
For an example, lets say we have the following class to be tested.
public final class ConcatenatingInputStream extends InputStream {
private final Queue<InputStream> sequentialInputs;
private InputStream currentInput;
public ConcatenatingInputStream(InputStream... sequentialInputs) {
this.sequentialInputs = new LinkedList<InputStream>(Arrays.asList(sequentialInputs));
currentInput = this.sequentialInputs.poll();
}
@Override
public int read() throws IOException {
if (currentInput == null) return -1;
int nextByte = currentInput.read();
if (nextByte >= 0) {
return nextByte;
}
currentInput = sequentialInputs.poll();
return read();
}
}
This class could easily be tested without mocking by using ByteArrayInputStream
objects for input, but lets say
we want to make sure that the InputStream#read()
method is properly invoked on each input stream passed in the constructor.
The following test will achieve this.
@Test
void concatenateInputStreams(@Injectable InputStream input1, @Injectable InputStream input2) throws Exception {
new Expectations() {{
input1.read(); returns(1, 2, -1);
input2.read(); returns(3, -1);
}};
InputStream concatenatedInput = new ConcatenatingInputStream(input1, input2);
byte[] buf = new byte[3];
concatenatedInput.read(buf);
assertArrayEquals(new byte[] {1, 2, 3}, buf);
}
Note that the use of @Injectable
is indeed necessary here, since the class under test extends the mocked
class, and the method called to exercise ConcatenatingInputStream
is actually defined in the base
InputStream
class.
If InputStream
was mocked "normally", the read(byte[])
method would always be mocked, regardless
of the instance on which it is called.
When using @Mocked
or @Capturing
(and not
@Injectable
on the same mock field/parameter), we can still match replay invocations to expectations
recorded on specific mocked instances.
For that, we simply declare multiple mock fields or parameters of the same mocked type, as the next example shows.
@Test
void matchOnMockInstance(@Mocked Collaborator mock, @Mocked Collaborator otherInstance) {
new Expectations() {{ mock.getValue(); result = 12; }};
// Exercise code under test with mocked instance passed from the test:
int result = mock.getValue();
assertEquals(12, result);
// If another instance is created inside code under test...
Collaborator another = new Collaborator();
// ...we won't get the recorded result, but the default one:
assertEquals(0, another.getValue());
}
The test above will only pass if the tested code (here embedded in the test method itself, for brevity) invokes getValue()
on the exact same instance on which the recording invocation was made.
This is typically useful when the code under test makes calls on two or more different instances of the same type, and the test wants to
verify that a particular invocation occurred on the expected instance.
For future instances that will later get created by code under test, there is a way we can match invocations on them to separate
recorded expectations (assuming a test needs varying behavior from said instances).
This is done by recording expectations on specific constructor invocations of the mocked class, and then simply using the new
instances obtained from such "new
" expressions when recording expectations on their instance methods.
Lets see an example.
@Test
void newCollaboratorsWithDifferentBehaviors(@Mocked Collaborator anyCollaborator) {
// Record different behaviors for each set of instances:
new Expectations() {{
// One set, for instances created with "a value":
Collaborator col1 = new Collaborator("a value");
col1.doSomething(anyInt); result = 123;
// Another set, for instances created with "another value":
Collaborator col2 = new Collaborator("another value");
col2.doSomething(anyInt); result = new InvalidStateException();
}};
// Code under test:
new Collaborator("a value").doSomething(5); // will return 123
...
new Collaborator("another value").doSomething(0); // will throw the exception
...
}
In the above test, we declare a mock field or mock parameter of the desired class, using @Mocked
.
This mock field/parameter, however, is not used when recording expectations; instead, we use the instances created on
instantiation recordings to record further expectations on instance methods.
The future instances created with matching constructor invocations will map to those recorded instances.
Also, note that it's not necessarily a one-to-one mapping, but a many-to-one mapping, from potentially many future instances to a single
instance used for recorded expectations.
By default, all methods which can be called on a mocked instance get mocked. This is appropriate for most tests, but in some situations we might need to select only certain methods to be mocked. Methods not mocked in an otherwise mocked instance will execute normally when called.
When an object is partially mocked, JMockit decides whether to execute the real implementation of a method as it gets called from the code under test, based on which expectations were recorded and which were not. The following example tests will demonstrate it.
class PartialMockingTest {
static class Collaborator {
final int value;
Collaborator(int value) { this.value = value; }
int getValue() { return value; }
final boolean simpleOperation(int a, String b, Date c) { return true; }
void doSomething() { ... }
}
@Test
void partiallyMockingASingleInstance() {
Collaborator collaborator = new Collaborator(2);
new Expectations(collaborator) {{ // one or more instances to be partially mocked
collaborator.getValue(); result = 123;
collaborator.simpleOperation(1, "", null); result = false;
}};
// Mocked (instance methods recorded on one of the given instances):
assertEquals(123, collaborator.getValue());
assertFalse(collaborator.simpleOperation(1, "", null));
// Not mocked (unrecorded instance methods, static methods, constructors, and different instances):
collaborator.doSomething();
assertEquals(45, new Collaborator(45).getValue());
}
}
As shown above, the Expectations(Object...)
constructor accepts one or more objects to be partially mocked.
In case a test wants to partially mock the constructors and/or static
methods of a class, a MockUp
(from the
Faking API) would have to be used.
Notice that in this example test there is no mock field or mock parameter - so, the partial mocking constructor effectively provides yet another way to apply mocking.
It should be noted that, when we request an instance to be partially mocked, it can also have invocations verified on it, even if the verified methods were not recorded. For example, consider the following test.
@Test
void partiallyMockingAnObjectJustForVerifications() {
Collaborator collaborator = new Collaborator(123);
new Expectations(collaborator) {};
// No expectations were recorded, so nothing will be mocked.
int value = collaborator.getValue(); // value == 123
collaborator.simpleOperation(45, "testing", new Date());
...
// Unmocked methods can still be verified:
new Verifications() {{ c1.simpleOperation(anyInt, anyString, (Date) any); }};
}
Finally, a simpler way to apply partial mocking to a tested class is to have a field in the test class annotated as both
@Tested
(see section below) and @Mocked
.
In this case, the tested object is not passed to the Expectations
constructor, but we still need to record
expectations on any methods requiring mocked results.
Our discussion of this feature will be based on the (contrived) code below.
public interface Service { int doSomething(); }
final class ServiceImpl implements Service { public int doSomething() { return 1; } }
public final class TestedUnit {
private final Service service1 = new ServiceImpl();
private final Service service2 = new Service() { public int doSomething() { return 2; } };
public int businessOperation() {
return service1.doSomething() + service2.doSomething();
}
}
The method we want to test, businessOperation()
, uses classes that implement a separate interface,
Service
.
One of these implementations is defined through an anonymous inner class, which is completely inaccessible (except for the use of
Reflection) from client code.
Given a base type (be it an interface
, an abstract
class, or any sort of base class), we can write a test
which only knows about the base type but where all implementing/extending implementation classes get mocked.
To do so, we declare a "capturing" mocked type which refers only to the known base type.
Not only will implementation classes already loaded by the JVM get mocked, but also any additional classes that happen to get loaded by
the JVM during later test execution.
This ability is activated by the @Capturing
annotation, which can be applied to mock fields and mock
parameters, as demonstrated below.
final class UnitTest {
@Capturing Service anyService;
@Test
void mockingImplementationClassesFromAGivenBaseType() {
new Expectations() {{ anyService.doSomething(); returns(3, 4); }};
int result = new TestedUnit().businessOperation();
assertEquals(7, result);
}
}
In the test above, two return values are specified for the Service#doSomething()
method.
This expectation will match all invocations to this method, regardless of the actual instance on which the invocation occurs,
and regardless of the actual class implementing the method.