JMockit: The Basics

This is a guide to quickly getting started with JMockit.  

Motivating Example

Consider unit testing the following code:
public class Y2KChecker {
    public void check() throws Y2KException {
        Calendar calendar = Calendar.getInstance();

        calendar.set(2000, 1, 1);

        Date date = calendar.getTime();

        if (date.getTime() == System.currentTimeMillis()) {
            throw new Y2KException("Y2K Bug!");
        }
    }

    public static class Y2KException extends Exception {
        public Y2KException(String reason) {
            super(reason);
        }
    }
}

Getting this code to throw the Y2KException is difficult without some sort of test double injection framework or dependency injection/inversion of control abstraction.   To illustrate the latter, we could refactor the code into something along these lines:
public class Y2KChecker {    
public static class Clock {
public long currentTimeMillis() {
return System.currentTimeMillis();
}
}

private final Clock clock;

public Y2KChecker(Clock clock) {
this.clock = clock;
}

public void check() throws Y2KException { Calendar calendar = Calendar.getInstance(); calendar.set(2000, 1, 1); Date date = calendar.getTime(); if (date.getTime() == clock.currentTimeMillis()) { throw new Y2KException("Y2K Bug!"); } } public static class Y2KException extends Exception { public Y2KException(String reason) { super(reason); } }
}
This code is now more testable.   To trigger the Y2KException, you could write a test case that looks like this:
public class Test {

@Test(expectedExceptions = Y2KChecker.Y2KException.class)
public void testCheck() {
Y2KChecker checker = new Y2KChecker(new Y2KChecker.Clock() {
private static final long Y2K_MILLIS = 949433850262L;
public long currentTimeMillis() {
return Y2K_MILLIS;
}
}

checker.check();
}
}

The problem with this approach, however, is that implementational details of the class under test have to be exposed in its public API in order to test it.   This problem is shared with most dependency injection/inversion of control approaches to unit testing.

Test double injection frameworks like JMockit introduce alternative mechanisms for replacing dependencies with test doubles without exposing implementational details of the class under test in its public API.

Set Up JMockit with Maven and TestNG

It's easy to setup JMockit with Maven and TestNG.   In our example POM we...
  1. Add a JMockit dependency and the JMockit Maven repository
  2. Add a TestNG dependency
  3. Add the Maven Surefire plugin to run our unit tests
  4. Add a testng.xml file where we will enumerate the test cases in our test suite
  5. Configure the Surefire plugin to run with the JMockit agent.   The agent is responsible for injecting our test doubles at runtime.
Minimal Maven POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.duderino.injection</groupId>
    <artifactId>injection</artifactId>
    <name>Injection</name>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <url>https://github.com/duderino/injection</url>

    <repositories>
        <repository>
            <id>jmockit-svn</id>
            <url>http://jmockit.googlecode.com/svn/maven-repo</url>
            <releases>
                <checksumPolicy>ignore</checksumPolicy>
            </releases>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>mockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>0.999.10</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.1.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <defaultGoal>test</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <useFile>false</useFile>
                    <suiteXmlFiles>
                        <suiteXmlFile>src/test/resources/org/duderino/injection/testng.xml</suiteXmlFile>
                    </suiteXmlFiles>
<argLine>
-javaagent:"${settings.localRepository}"/mockit/jmockit/0.999.10/jmockit-0.999.10.jar</argLine>
</configuration> </plugin> </plugins> </build> </project>
Providing you put your code and other resources in the standard directories, Maven will take care of the rest.

If you are running JDK 1.6+, you can omit the "<argLine>-javaagent..." line, but if you do so you'll see worrying warning messages like this:
WARNING: JMockit was initialized on demand, which may cause certain tests to fail;
please check the documentation for better ways to get it initialized.

As you add unit tests, add them to the testng.xml file.   Our testng.xml file looks like this:
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="Injection" verbose="1" parallel="false" thread-count="1">
    <test name="unit">
        <classes>
            <class name="org.duderino.injection.jmockit.basic.Y2KCheckerTest"/>
        </classes>
    </test>
</suite>

Create A Test Case

Now that our development environment is setup, let's write a test case to test the Y2KChecker class.

Y2KCheckerTest:
import mockit.Mock;
import mockit.Mockit;
import org.testng.annotations.Test;

public class Y2KCheckerTest {
    private static class MockSystem {
        private static final long Y2K_MILLIS = 949433850262L;

        @Mock
        public long currentTimeMillis() {
            return Y2K_MILLIS;
        }
    }

    @Test(expectedExceptions = Y2KChecker.Y2KException.class)
    public void testCheck() throws Exception {
        Y2KChecker checker = new Y2KChecker();

        Mockit.setUpMock(System.class, MockSystem.class);

        checker.check();
    }
}

The first thing to realize is that we didn't have to modify the Y2KChecker class in order to inject our test double.   We didn't have to create a wrapper for System.currentTimeMillis() either.   Instead we created a MockSystem test double with a replacement currentTimeMillis() method.   Note the @Mock annotation on the replacement currentTimeMillis() method.

Note also the Mockit.setupMock() call inside the test case.   This call will reroute all calls to System to our MockSystem at runtime.   When checker.check() is called, the JMockit agent will reroute the System.currentTimeMillis() call to MockSystem.currentTimeMillis();

Run The Unit Tests

Now let's run the test suite.   Because we put this...
<build>
    <defaultGoal>test</defaultGoal>
    ...
</build>
... in our Maven POM, all we have to do is type 'mvn' in our top-level directory and Maven will run the test suite after completing all its prerequisites (e.g., compiling both the code under test and the test cases):

$ mvn
[INFO] Scanning for projects...
[INFO]                                                                        
[INFO] ------------------------------------------------------------------------
[INFO] Building Injection 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ injection ---
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /Users/blattj/dev/injection/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ injection ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ injection ---
[WARNING] Using platform encoding (MacRoman actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ injection ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.9:test (default-test) @ injection ---
[INFO] Surefire report directory: /Users/blattj/dev/injection/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running TestSuite
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.798 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.205s
[INFO] Finished at: Wed Nov 23 11:49:47 PST 2011
[INFO] Final Memory: 4M/81M
[INFO] ------------------------------------------------------------------------

If this didn't work for you and you want to see a complete working example, our test code has been checked in at https://github.com/duderino/injection

Next Steps

If you want to keep going, see our JMockit Evaluation for a more detailed overview.  

See also the JMockit tutorial for official documentation.