Getting started | About | Tutorial | Samples | API | Source | Issues | Community | History |
---|
JMockit is open source software licensed under the MIT License. It includes mocking APIs and other tools for use in developer testing, that is, tests written by developers using a testing framework such as JUnit or TestNG.
The toolkit was created mainly as an attempt to overcome certain limitations found in "conventional" mocking tools, which prevented the creation of unit tests for code designed according to well-established OO practices. Another goal was to provide simpler and more succinct APIs for writing developer tests. In addition, and differently from other testing tools which specifically target the use of mocks, JMockit also includes other tools designed to support the creation of large test suites.
Internally, the Java SE 6+ bytecode instrumentation feature (the java.lang.instrument package) is used extensively. For the reading and in-memory generation of modified bytecode arrays, the ASM library is used.
Mocking features mostly use the JVM's ability to redefine classes that have already been loaded, as exposed
through the java.lang.instrument.Instrumentation#redefineClasses(ClassDefinition...)
method.
This method uses the same mechanism behind the "hotswap" feature commonly used while debugging Java code, which
allows a new version of a class to take the place of its current version in a running JVM.
Whenever a class to be mocked is found in the course of a test run, it has its current bytecode definition temporarily modified, so that the regular behavior of methods and constructors can be replaced with mock behavior, as specified in a test. As soon as the test is completed, the class has its bytecode restored, and its original behavior is resumed. This process repeats itself for the next test, whenever needed as specified in test code through the mocking API.
The JMockit approach is an alternative to the conventional use of "mock objects" as provided by tools such as EasyMock, jMock, and Mockito.
Such tools are based on the dynamic generation of implementation classes (through
java.lang.reflect.Proxy, when
given an interface to be mocked) and subclasses (through CGLIB, when given
a non-final class to be mocked).
This particular mocking implementation technique, in which a subclass with overriding mock methods is created,
carries some penalties: final
classes, constructors, and non-overridable methods simply cannot
be mocked; even worse, when mocking a non-final class which happens to contain final
methods, these
tools will silently ignore them, often surprising users.
Most importantly, however, these limitations mean that dependencies of code under test (that is, the objects of other
classes on which a given class under test depends) must be controlled by the tests, so that mock instances can be
passed to the clients of those dependencies;
that is, dependencies cannot simply be instantiated with the new
operator in the client class.
Ultimately, the technical limitations of conventional mocking tools impose the following design restrictions on production code:
final
.
static
or final
methods on said dependencies, nor
directly instantiate them.
The problem with imposing restrictions on the design of production code is that there are good reasons for
making classes and methods final
, for directly obtaining instances of collaborators with the
new
operator, and for using APIs containing static
methods.
There is nothing inherently wrong with using these three Java language keywords, after all.
final
Declaring classes or methods final
communicates the design decision that they are not intended for
extension by subclassing.
For example, in a correct application of the Template
Method design pattern, the "template" method itself (which implements an algorithm calling one or more
overridable methods) must not be overridable.
Also, from the point of view of API design, final
classes are the only ones which can be safely evolved;
otherwise, you risk running into the fragile base-class problem.
For more on this, see the following articles:
Why extends is evil,
Virtuality.
In practice, most classes and methods in an application or even in a reusable class library are not designed with
extension by subclassing in mind, so it makes sense to declare them as final
; probably that's why most
other OO languages (C++, C#, etc.) make all methods non-overridable by default.
Another benefit of making Java classes or methods final
is that a good static analysis tool will be able
to provide more useful feedback.
The code inspections associated with the use of final
in IntelliJ IDEA, for example, led me numerous
times to simplify and even eliminate unused parts of the code base, when developing large business applications.
(For the curious, some of the relevant inspections are: "Class structure: 'protected' member in 'final' class",
"Declaration redundancy: redundant throws declaration", and "Initialization issues: overridable method call during
object construction".)
For an authoritative discussion on design for extension (which defends the judicious use of final
for
classes and methods), see "Item 17: Design and document for inheritance or else prohibit it" in the
Effective Java book.
Another book which strongly recommends the use of final
for classes and methods is
Practical API Design
(see "Make Everything Final", in chapter 5).
Yet another relevant book is API Design for C++, which is also largely
applicable to Java (see sections "2.3.2 Add Virtual Functions Judiciously" and "4.6.3 Using Inheritance").
The site for this last book points to several interesting articles, including
Java API Design Guidelines.
Personally, I consider the choice by the Java language designers to make non-private instance methods overridable
by default a mistake, one which was probably influenced by a mentality typical until the 1990's that the
more you can inherit, the better.
Personal experience, however, shows two things that discredit that position: 1) in most cases, Java programmers
simply take the easy route and leave all classes and methods as non-final, without consideration for API design
questions (just like for languages with the "virtual
" keyword, where most methods end up as
non-overridable simply because it was the default - the difference here, of course, being that this usually is the
right choice); 2) often, when a programmer decides to create a subclass for a non-final Java class, it is done only
as a "hack" intended to arbitrarily change the behavior of some method, without any consideration to object-oriented
design principles such as the Liskov Substitution
Principle (LSP), or the recommendation to "Favor object composition over class inheritance"
(Design Patterns, page 20).
new
Directly instantiating dependencies with new
facilitates the use of stateful objects.
In OO design, objects normally are not stateless.
However, when dependencies are obtained or injected through external means (such as a "Service Locator" or a DI
framework) there is a tendency to make them stateless, and with a single global instance.
Such a practice leads to code that is more procedural and less object oriented.
Of course, this criticism applies to dependencies whose interfaces have only one implementation in production code,
or when the selection of an implementation between many would not benefit from external configuration (for example,
people typically instantiate List
implementations directly, even if they code to the abstract
interface only); situations where one implementation must be selected through external configuration tend to
be quite rare in practice.
Related to this issue, I think, there is a widely observed misunderstanding of just what the phrase "Program to
an interface, not an implementation" (from the famous "Design Patterns" book, page 18) actually means.
Many seem to think that one should create a new separate Java interface (or abstract class) for any concrete
class which is being created or doesn't yet implement one.
In reality, it was only meant as a recommendation to avoid declaring variables, fields, parameters, or return types
as the implementation type when an abstract type already exists (for example, don't declare variables of
type ArrayList
if all you need is a List
).
The "Effective Java" book mentioned above also discusses this topic, in "Item 52: Refer to objects by their
interfaces".
Another bad motivation to avoid the use of new
may simply be an unreasonable fear of getting "too much"
garbage collection.
The truth, however, is that JVM implementations are (and have been for many years) very good at efficiently managing
short-lived objects.
Using single-instance classes as a rudimentary object caching mechanism is a terrible use of the Java platform.
From my experience, this practice not only leads to anti-OO code but also to big, un-cohesive, stateless classes with
many methods, the exact opposite of small, cohesive, and stateful classes which are what true objects are about.
static
The use of classes containing only static
methods is the best choice when none of the methods operate on
any state (for example, the Math
class), or the actual state is stored in a separate context object,
such as the HTTP request context or the persistence context.
For this last situation, consider an application that has a persistence subsystem which provides access to a
relational database.
One possibility is to inject instances of work unit objects (an Hibernate Session
, or a JPA
EntityManager
) wherever they are needed, and use the persistence API directly.
A better approach, in my experience, is to use a static facade which encapsulates all access from the
application to the persistence subsystem.
The benefits of this are many, not the least of which is that it actually provides more flexibility than the
more direct, instance-based, approach, while providing a tight and easily accessible client API.
Another recommended usage of static methods is presented in "Item 1: Consider static factory methods instead of constructors", again in the "Effective Java" book.
The limitations found in conventional mocking tools have come to be associated with the idea of "untestable code". Often, we see the restrictions resulting from those limitations considered as inevitable, or even as something that could be beneficial. The JMockit toolkit, which breaks away from these limitations and restrictions, shows that in fact there is no such thing as truly untestable code. There is, of course, code that is harder to test because it is too complicated and convoluted, lacks cohesion, and so on and so forth.
Therefore, by eliminating the technical limitations traditionally involved in the isolation of a unit from its dependencies, we get the benefit that no artificial design restrictions must be imposed on production code for the sake of unit testing. Additionally, it becomes possible to write unit tests for legacy code, without the need for any prior adaptation or refactoring. The ability to create automated regression tests for existing code before refactoring it is extremely valuable, as implied by the very definition of the term. In short, with a less restrictive mock testing tool the testability of production code becomes less of an issue, giving developers more freedom to use the language and more design choices when creating new code, while facilitating the initial creation of tests for legacy code.
Another way of thinking about testability is to differentiate between intrinsic and extrinsic testability. In the first case, we can conclude that whatever makes the code more or less maintainable also makes it more or less easily testable, and vice-versa. For example, methods with higher cyclomatic complexity (basically, the number of different execution paths through the method) require more tests to be fully covered, while at the same time being more difficult to understand and change; in other words, intrinsic testability is not particularly useful as a separate measure for code quality, if it is nothing more than maintainability. In the second case, extrinsic testability, we are not really considering properties of the production code, but instead properties of the tools used to test that code. Therefore, when a tool for testing code in isolation provides an easy to use API that is able to deal with all possible isolation scenarios, such extrinsic concerns for testability completely disappear.
In addition to mocking APIs, the JMockit toolkit includes its own code coverage tool, JMockit Coverage. This tool aims to provide the following benefits.
-D
" or its
Ant/Maven equivalent).
coverage-output
" system property.
For the generation of the HTML report, Java source files are automatically searched in all "src" directories
under the working directory.
In short, the two main differentiating factors for JMockit Coverage are that 1) it generates the coverage report as easily as possible and without depending on special conditions or requiring specific plugins for Java IDEs or build tools; and 2) it provides newer and more sophisticated coverage metrics, such as path coverage and true line coverage.