JMockit An automated testing toolkit for Java

About the JMockit Testing Toolkit

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.

How it works

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.

Conventional tools for mock objects

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:

  1. Each class which may need to be mocked in a test must either implement a separate interface or not be final.
  2. The dependencies of each class to be tested must either be obtained through configurable instance creation methods (factories or a Service Locator), or be exposed for dependency injection. Otherwise, tests won't be able to pass mock implementations of dependencies to code under test.
  3. Since only non-final instance methods can be mocked, classes to be unit tested in isolation from their dependencies cannot call any static or final methods on said dependencies, nor directly instantiate them.

Design considerations for production code

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.

Use of 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).

Use of 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.

Use of 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.

Testability

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.

Code Coverage

In addition to mocking APIs, the JMockit toolkit includes its own code coverage tool, JMockit Coverage. This tool aims to provide the following benefits.

  1. Bytecode modification performed only at runtime, therefore avoiding the creation of undesirable files. No extra source or class files are created, and no coverage data file is generated unless explicitly requested. The only files created or modified are those that the user really wants as the desired output.
  2. Running tests with JMockit Coverage does not require the use of any particular command line script, Ant task, Maven plugin, or IDE-specific plugin. The tool applies the idea of convention over configuration, trying to make it as easy as possible for the developer to run tests with code coverage: by simply adding one jar file to the classpath, and optionally specifying certain initialization parameters to the JVM (system properties settable with "-D" or its Ant/Maven equivalent).
  3. No need to specify which classes should be considered for coverage and which should not (by default, as they can be explicitly specified if needed). All code under test will automatically be analyzed for coverage. Specifically, the tool will gather coverage data for all production code executed by the test suite, excluding classes defined inside jar files (on the assumption that such code belongs to libraries used by the code under test).
  4. An HTML report and/or a serialized output file can be easily generated at the end of each test run. The first requires no extra configuration as it is the default form of coverage output; the second only requires setting the "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.
  5. In code coverage reports, each line of production code can be accurately linked to the lines in test code which caused their execution, for each individual execution of each line of code. (However, this feature is inactive by default, because of the higher runtime cost and the larger resulting HTML report.)
  6. Besides a line coverage metric, a path coverage metric is also made available. Both metrics are calculated and shown at the source file, package, and global levels. For path coverage, each possible path through a method or constructor can be interactively displayed in the HTML report.
  7. Intra-line coverage is provided, with the appropriate red/green coloring in the HTML report for each conditionally executed line segment.

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.