Learn to create and inject mocks, creating expectations and verifications using JMockit library in JUnit tests. We will begin with the basic concepts of JMockit with an example, and later dig deeper into the advance concepts.
1. JMockit’s Core Concepts
1.1. Core Features
JMockit is open-source software that contains support for mocking, faking, and integration testing, and a code coverage tool. It is used for mocking the external dependencies outside the test boundary, similar to Mockito and other such mocking libraries.
The most important feature of JMockit is that it lets us mock anything, even the things that are hard to mock with other libraries such as private methods, constructors, static and final methods. It even allows mocking the member fields and initialization blocks as well.
1.2. Test Stages
Similar to EasyMock, JMockit also uses the Record-Replay-Verify model in a test after the mocks and SUT (System Under Test) have been defined.
- Record: In this step, we record the expectations from the mock objects. We define the behavior of mock objects i.e. method to be invoked, the return value and how many times we expect it to be invoked.
- Replay: In this step, we execute the actual test code as written in SUT (System Under Test).
- Verify: In this step, we verify that all the expectations were executed or not.
A typical JMockit test will look like this:
public class TestClass {
@Tested
private Service service;
@Injectable
private Dao dao;
@Mock
private Component component;
@Test
public void testSUT() {
// Test data initialization, if any
new Expectations() {{
// define expected behaviour for mocks and injectables
}};
// test service operations
new Verifications() {{
// verify mocks and injectables
}};
// assertions
}
}
1.3. Declarative Expectations and Verifications
JMockit allows defining the expectations and verifications in a very elaborative and declarative manner. These are very easy to distinguish from the rest of the test code.
Other mocking libraries, in general, provide static
methods such as expect()
, andThenReturn()
and times()
to specify the expectations, and verify()
for verifying the expectations after the test execution.
MockAPI.expect(mock.method(argumentMatcher)).andThenReturn(value).times(1);
In contrast, JMockit expresses them using the following classes:
- Expectations: An Expectations block represents a set of invocations to a specific mocked method/constructor that is relevant for a given test.
- Verifications: A regular unordered block to check that at least one matching invocation occurred during replay.
- VerificationsInOrder: It should be used when we want to test the actual relative order of invocations during the replay phase.
- FullVerfications: If we want to have all invocations to the mocked types/instances involved in a test verified. It will make sure that no invocations are left unverified.
We will revisit these classes again later in this tutorial.
2. A Simple JMockit Test Example
2.1. Maven Dependency
Start with including the JMockit dependency in the application. If not included already, add JUnit dependencies as well.
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.49</version>
</dependency>
2.2. System Under Test
To demo the JMockit syntax, we have created a typical usecase where a RecordService invokes RecordDao to save a Record, and send a notification to using NotificationService. The RecordService uses a SequenceGenerator class to get the next record id.
You can browse the code in the GitHub repository whose link is present at the end of this tutorial.
2.3. Test Demo
To test RecordService.saveRecord() method, we need to inject RecordDao and SequenceGenerator as dependencies in it. The RecordService gets NotificationService instance in runtime so we can simply mock it and let runtime replace it with a mock.
Next, we will create some Expectations, execute the test code and, finally, execute Verifications to conclude the test. We can use additional JUnit assertions to verify additional test outcomes.
public class JMockitDemoTests {
@Injectable
RecordDao mockDao; // Dependency
@Injectable
SequenceGenerator mockGenerator; // Dependency
@Tested
RecordService service; //System Under Test
// NotificationService can be mocked in test scope
@Test
public void testSaveRecord(@Mocked NotificationService notificationService) {
Record record = new Record();
record.setName("Test Record");
//Register Expectations
new Expectations() {{
mockGenerator.getNext();
result = 100L;
times = 1;
}};
new Expectations() {{
mockDao.saveRecord(record);
result = record;
times = 1;
}};
new Expectations() {{
notificationService.sendNotification(anyString);
result = true;
times = 1;
}};
//Test code
Record savedRecord = service.saveRecord(record);
// Verifications
new Verifications() {{ // a "verification block"
mockGenerator.getNext();
times = 1;
}};
new Verifications() {{
mockDao.saveRecord(record);
times = 1;
}};
new Verifications() {{
notificationService.sendNotification(anyString);
times = 1;
}};
//Additional assertions
assertEquals("Test Record", savedRecord.getName());
assertEquals(100L, savedRecord.getId());
}
}
3. Creating and Injecting Mocks
It is worth remembering that JMockit allows having different mock objects in different phases on the record-replay-verify flow. For example, we can have two mocks for a dependency and use them separately in expectations and verifications.
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.
@Mocked Dependency mockDependency;
@Test
public void testCase(@Mocked Dependency anotherMockDependency)
{
new Expectations() {{
mockDependency.operation();
}};
// Call the code under test
new Verifications() {{
anotherMockDependency.operation();
}};
}
JMockit allows different ways to create and inject mocks for SUT. Let’s learn about them.
3.1. Mocking Related Annotations
The primary annotations for mocking the dependencies are as follows.
3.1.1. @Mocked and @Capturing
When used on a field, @Mocked will create mocked instances of each and every new object of that particular class during the test execution. Internally, it will mock all methods and constructors on all instances of a mocked class.
@Mocked Dependency mockDependency;
@Capturing
behaves similar to @Mocked, but additionally, @Capturing mocks every subclass extending or implementing the annotated field’s type.
In the following example, JMockit will mock all the instances of Dependency as well as any subclasses of it. If Dependency is an interface then JMockit will mock all its implementing classes.
@Capturing Dependency mockDependency;
Note that the mock fields annotated only with @Mocked or @Capturing are not considered for injection.
3.1.2. @Injectable and @Tested
The @Tested annotation triggers the automatic instantiation and injection of other mocks and injectables, just before the execution of a test method. An instance will be created using a suitable constructor of the tested class, while making sure its internal @Injectable dependencies get properly injected (when applicable).
As opposed to @Mocked and @Capturing, @Injectable creates only one mocked instance.
Note that while initializing the tested classes, JMockit supports two forms of injection: i.e. constructor injection and field injection.
In the following example, dep1 and dep2 will be injected into SUT.
public class TestClass {
@Tested SUT tested;
@Injectable Dependency dep1;
@Injectable AnotherDependency dep2;
}
3.2. Test Class and Method Scoped Mocks
JMockit allows creating mocks at the class level as well as at the test method level by passing the mocks as test parameters. Method level mocks help in creating a mock just for one test and thus help in further limiting the test boundaries.
public class TestClass {
//Class scoped mock
@Mocked Dependency mock;
//Method scoped mock
@Test
public void testCase(@Mocked AnotherDependency anotherMock)
{
//test code
}
}
4. Recording Expectations
4.1. Matching Method Invocations
JMockit is very flexible in recording the expectations. We can record multiple method invocations in a single Expectations block and also, and we can record multiple Expectations blocks in a single test method.
public TestClass {
new Expectations() {{
mock.method1();
mock.method2();
anotherMock.method3();
}};
new Expectations() {{
someOtherMock.method();
}};
}
4.2. Matching Arguments
Using the exact arguments in method invocations will match the exact argument values in the replay phase. The object-type arguments are checked equal using the equals()
method. Similarly, arrays and lists type arguments are treated as equals if both arrays or lists are of the same size and contain similar elements.
For flexible argument matching, we can use one of the following two approaches:
4.2.1. any
Fields
JMockit provides a range of any argument matching fields. They support one for each primitive type (and the corresponding wrapper class), one for strings, and one for all Objects.
new Expectations() {{
mock.method1(anyInt);
mock.method2(anyString);
mock.method3(anyInt);
mock.method4((List<?>) any);
mockDao.saveRecord((Record) any);
}};
4.2.2. with
Methods
We can use a withXYZ() method from a range of such methods for specific usages. These methods are withEqual(), withNotEqual(), withNull(), withNotNull(), withSubstring(), withPrefix(), withSuffix(), withMatch(regex), withSameInstance(), withInstanceLike() and withInstanceOf() etc.
new Expectations() {{
mock.method1(withSubstring("xyz"));
mock.method2(withSameInstance(record));
mock.method3(withAny(1L)); //Any long value will match
mock.method4((List<?>) withNotNull());
}};
4.3. Matching Return Values
If non-void mock methods, we can record the return values in result
field. The assignment to result should appear right after the invocation that identifies the recorded expectation.
new Expectations() {{
mock.method1();
result = value1;
mock.method2();
result = value2;
}};
If we are invoking a method in a loop then we can expect multiple return values either using returns(v1, v2, …) method or assigning a list of values to result field.
new Expectations() {{
mock.method();
returns(value1, value2, value3);
}};
If the test instead needs an exception or error to be thrown when the method is invoked, simply assign the desired throwable instance to result.
new Expectations() {{
mock.method();
result = new ApplicationException();
}};
4.3. Matching Invocation Count
JMockit provides three special fields just matching the invocation counts. Any invocations less or more than the expected lower or upper limit, respectively, and the test execution will automatically fail.
times
minTimes
maxTimes
new Expectations() {{
mock.method();
result = value;
times = 1;
}};
5. Writing Verifications
5.1. Verifications
Inside a Verifications blocks, we can use the same steps that are available in a Expectations blocks except for the return values and thrown exceptions. We can reuse the method invocations and count from the expectations.
So, the syntax to write verifications is the same as expectations and you can refer to previous sections for the same.
new Verifications() {{
mock.method();
times = 1;
}};
5.2. VerificationsInOrder
As mentioned in section 1.3, this helps to test the actual relative order of invocations during the replay phase. Inside this block, simply write the invocations to one or more mocks in the order they are expected to have occurred.
@Test
public void testCase() {
//Expectation
//Test code
mock.firstInvokeThis();
mock.thenInvokeThis();
mock.finallyInvokeThis();
//Verification
new VerificationsInOrder() {{
mock.firstInvokeThis();
mock.thenInvokeThis();
mock.finallyInvokeThis();
}};
}
5.3. FullVerifications
In the previous verification modes, JMockit verifies that all invocations in the Verifications block must be executed at least once during the test replay phase. It does not complain about those invocations that happened in the replay phase but were not added in the Verifications block.
In the following example, method3() has been executed in the test but not verified in the verification phase. The test will PASS.
@Test
public void testCase() {
//Test code
mock.method1();
mock.method2();
mock.method3();
//Verification
new VerificationsInOrder() {{
mock.method1();
mock.method2();
}};
}
If we want to take total control of the mock interactions then we can FullVerifications. It helps in preventing any method to be executed that we are not verifying.
@Test
public void testCase() {
//Test code
mock.method1();
mock.method2();
mock.method3();
//Verification
new FullVerifications() {{
mock.method1();
mock.method2();
mock.method3(); //If we remove this, the test will FAIL
}};
}
6. Conclusion
In this tutorial, we learned to use the mocking functionality provided by JMockit, in detail. We learned about the record-replay-verify phases in-depth and with examples.
We also learned the advance concepts such as flexible argument matching and invocation counts.
Happy Learning !!