The toolkit is provided as a set of artifacts deployed to the Maven Central repository. It requires Java 7 or newer for test execution; tests must use JUnit or TestNG. For instructions on how to add the library to a Java project, see Running tests with JMockit.
In this tutorial we examine the APIs available in the library, with the help of example tests (using Java 8). The central API - a single annotation - provides support for the automatic instantiation and initialization of the objects to be tested. Then we have the mocking API (also known as the "Expectations" API), intended for tests which use mocked dependencies. Finally, there is a small faking API (aka the "Mockups" API), which can be used for the creation and application of fake implementations that avoid the full cost of external components.
Even though the tutorial is fairly complete, it does not attempt to cover the entire published APIs in detail.
A complete and detailed specification for all annotations, classes, methods, etc. is provided through the API documentation.
The "jmockit-1.x-sources.jar
" library file (downloadable from the Maven Central repository), contains Java source files
(with Javadoc comments) for easy access to the API source code and documentation from a Java IDE.
A separate chapter covers the code coverage tool.
Automated developer tests are those written by the developers themselves, to test their own code. They are usually written with the help of a testing framework, such as JUnit or TestNG; JMockit supports both.
Automated developer tests can be divided in two broad categories, whether they target individual program "units", multiple such units working together, or an entire "slice" of the system under test (the "SUT").
Even though subcutaneous tests include the interaction between multiple units, particular tests may not be interested in exercising all components, layers, or sub-systems involved. Such parts of the system may then be "mocked away" (or faked), so that the code under test runs in isolation from them. Therefore, the ability to isolate the code under test from certain parts of the system is generally useful in all kinds of tests. That said, in general it's best to make a test as "realistic" as we can. So, the use of mocking and/or faking should, ideally, be kept to a minimum.
A common and powerful technique for testing code in isolation is the use of "mocks". Traditionally, a mock object is an instance of a class specifically implemented for a single test or set of related tests. This instance is passed to code under test to take the place of one of its dependencies. Each mock object behaves in the way expected by both the code under test and the tests that use it, so that all tests can pass.
JMockit goes beyond conventional mock objects by allowing methods and constructors to be mocked directly on "real" (non-mock)
classes, eliminating the need to instantiate mock objects in tests and pass them to code under test; instead, objects created by code
under test will execute the mock behavior defined by tests, whenever methods or constructors are called on the real classes.
When a class is mocked, the original implementations of existing methods/constructors are temporarily replaced with mock implementations,
usually for the duration of a single test.
This mocking approach applies not only to public
instance methods, but also to final
and static
methods, as well as constructors.
Consider a business service class which provides a business operation with the following steps:
The first two steps require access to the application database, which is done through a simplified API to the persistence subsystem (which itself uses JPA). The third one can be achieved with a third-party API for sending e-mail, which in this example is Apache's Commons Email library.
Therefore, the service class has two separate dependencies, one for persistence and another for e-mail. To test the business operation, we rely on JMockit's JPA support to deal with persistence, while mocking the e-mail API. Complete source code for a working solution - with all tests - is available online.
package tutorial.domain;
import java.math.*;
import java.util.*;
import org.apache.commons.mail.*;
import static tutorial.persistence.Database.*;
public final class MyBusinessService
{
private final EntityX data;
public MyBusinessService(EntityX data) { this.data = data; }
public void doBusinessOperationXyz() throws EmailException {
List<EntityX> items =
(1) find("select item from EntityX item where item.someProperty = ?1", data.getSomeProperty());
// Compute or obtain from another service a total value for the new persistent entity:
BigDecimal total = ...
data.setTotal(total);
(2) persist(data);
sendNotificationEmail(items);
}
private void sendNotificationEmail(List<EntityX> items) throws EmailException {
Email email = new SimpleEmail();
email.setSubject("Notification about processing of ...");
(3) email.addTo(data.getCustomerEmail());
// Other e-mail parameters, such as the host name of the mail server, have defaults defined
// through external configuration.
String message = buildNotificationMessage(items);
email.setMsg(message);
(4) email.send();
}
private String buildNotificationMessage(List<EntityX> items) { ... }
}
The Database
class contains only static methods and a private constructor; the find
and
persist
methods should be obvious, so we won't list them here.
So, how can we test the "doBusinessOperationXyz" method without making any changes to the existing application code?
In the following JUnit test class, each test will verify the correct execution of persistence operations, as well as the expected
invocations to the e-mail API.
These verification points are the ones numbered (1)
-(4)
as indicated above.
package tutorial.domain;
import org.apache.commons.mail.*;
import static tutorial.persistence.Database.*;
import org.junit.*;
import org.junit.rules.*;
import static org.junit.Assert.*;
import mockit.*;
public final class MyBusinessServiceTest
{
@Rule public final ExpectedException thrown = ExpectedException.none();
@Tested final EntityX data = new EntityX(1, "abc", "someone@somewhere.com");
@Tested(fullyInitialized = true) MyBusinessService businessService;
@Mocked SimpleEmail anyEmail;
@Test
public void doBusinessOperationXyz() throws Exception {
EntityX existingItem = new EntityX(1, "AX5", "abc@xpta.net");
(1) persist(existingItem);
businessService.doBusinessOperationXyz();
(2) assertNotEquals(0, data.getId()); // implies "data" was persisted
(4) new Verifications() {{ anyEmail.send(); times = 1; }};
}
@Test
public void doBusinessOperationXyzWithInvalidEmailAddress() throws Exception {
String email = "invalid address";
data.setCustomerEmail(email);
(3) new Expectations() {{ anyEmail.addTo(email); result = new EmailException(); }};
thrown.expect(EmailException.class);
businessService.doBusinessOperationXyz();
}
}
The example test uses a couple of annotations from the JMockit API.
@Tested
takes care of setting up properly initialized objects to be tested, while
@Mocked
applies mocking to a given type.
As also shown in the test, the recording (inside a new Expectations() {{ ... }}
block) and the verification (inside a
new Verifications() {{ ... }} block
) of expectations is achieved simply by invoking the desired methods (as well as
constructors, even if not shown here) on mocked types/instances from inside the recording or verification block.
Values to return (or exceptions to throw) from matching invocations executed by the code under test are specified during recording
through the "result
" field.
Invocation count constraints can be specified, either when recording or when verifying, through API field assignments like
"times = 1
".
To run tests that use any of the JMockit APIs, use your Java IDE, Maven/Gradle build script, etc. the way you normally would. In principle, any JDK of version 1.7 or newer, on Windows, Mac OS X, or Linux, can be used. JMockit supports (and requires) the use of JUnit (version 4 or 5) or TestNG; specifically, you need to:
jmockit
dependency/jar to the test classpath.
-javaagent:<proper path>/jmockit.1.x.jar
" initialization
parameter.
It can be specified in the build script file for tools such as Maven or Gradle, or in a "Run/Debug Configuration" for IntelliJ IDEA or
Eclipse.
The JMockit artifacts are located in the central Maven repository.
To use them in a test suite, add the following to your pom.xml
file:
<dependencies>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Make sure the specified version (here specified in the "jmockit.version
" property) is the one you actually want.
Find the current version in the development history page.
JMockit also requires the -javaagent
JVM initialization parameter to be used; when using the Maven Surefire
plugin for test execution, it's specified as follows:
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version> <!-- or some other version -->
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
</argLine>
</configuration>
</plugin>
</plugins>
For information on using JMockit Coverage with Maven, see the relevant section in that chapter.
Gradle will also download the necessary artifacts from the mavenCentral()
repository.
In your gradle.build
file, add the jmockit
dependency and test configuration, replacing the number of the
desired version if needed:
repositories {
mavenCentral()
}
def jmockitVersion = '1.xy'
dependencies {
... "compile" dependencies ...
testImplementation "org.jmockit:jmockit:$jmockitVersion"
}
test {
jvmArgs "-javaagent:${classpath.find { it.name.contains("jmockit") }.absolutePath}"
}