State-based testing with JMockit

  1. Mock methods and mock classes
  2. Setting up mocks for a test
    1. Which kinds of methods can have mocks?
    2. In-line mock classes
  3. Mocking interfaces
  4. Invocation count constraints
  5. Mocking class initializers
  6. Accessing the invocation context
  7. Reentrant mocks
  8. Reusing mocks between tests
    1. Using before/after methods
    2. Reusable mock classes

In the JMockit toolkit, the Mockups API provides support for the creation of state-based tests and for the creation of fake implementations.

In state-oriented mocking, the focus is not on the interactions between a tested class and its dependencies, but on the data items received by and returned from those dependencies. Rather than writing a test from the perspective of the tested class, we write it from the perspective of the used classes, regardless of who uses them. Such tests extend regular state verification, which is typically performed through JUnit/TestNG assertions written at the end of a test method, with assertions on parameter values that get executed at the time a mocked method/constructor of a dependency is invoked by the tested class.

As for fake implementations, they can be particularly useful in integration tests which depend on external entities or resources such as the network, the file system, etc. The faking of dependencies which access such external entities allows us to run the same integration test in two "modes": 1) a "real" mode, where all code (tested unit and its dependencies) is exercised normally; and 2) an "emulated" mode, where the "problematic" dependencies have their implementations replaced with fake ones, so that the test can succeed even with no network connection, no file system, or whatever external dependency it would need otherwise. The replacement of real implementations with fakes is completelly transparent to the code which uses those dependencies, and can be switched on and off for different test runs.

For the remaining of this chapter, lets say we want to write tests for an application that uses the javax.security.auth.login.LoginContext class (from the standard JAAS API) in order to authenticate users. In this case, we don't want our tests to actually execute any of the real JAAS code, since it may depend on external configuration and might not easily work in a developer testing environment. Besides, we also don't want to exercise real JAAS code in a unit test because obviously that is not part of the application code we are trying to test. (Of course, in integration tests we just might work with a real JAAS setup.) Therefore, an application class depending on LoginContext will be our "unit under test", while the LoginContext class (the dependency) will have at least some of its methods and constructors "mocked" for any given test which involves authentication logic.

Mock methods and mock classes

In the context of the Mockups API, a mock method is any method in a mock class that gets annotated with @Mock. For short, in this chapter we refer to annotated mock methods simply as "mocks"; in other contexts, the word "mock" may refer to an instance of a mocked class instead. A mock class is any class extending the mockit.MockUp<T> generic base class, where <T> is the type to be mocked. The example below shows several mocks defined in a mock class for our example "real" class, javax.security.auth.login.LoginContext.

public final class MockLoginContext extends MockUp<LoginContext>
{
   @Mock
   public void $init(String name, CallbackHandler callbackHandler)
   {
      assertEquals("test", name);
      assertNotNull(callbackHandler);
   }

   @Mock
   public void login() {}

   @Mock
   public Subject getSubject() { return null; }
}

When a mock class is applied to a real class, the latter gets the implementation of those methods and constructors which have corresponding mocks temporarily replaced with the implementations of the matching mock methods, as defined in the mock class. In other words, the real class becomes "mocked" for the duration of the test which applied the mock class, and will respond accordingly whenever it receives invocations during test execution. At runtime, what really happens is that the execution of a mocked method/constructor is intercepted and redirected to the corresponding mock method, which then executes and returns (unless an exception/error is thrown) to the original caller, without this one noticing that a different method was actually executed. Normally, the "caller" class is the one under test, while the mocked class is a dependency.

Mock classes are often defined as nested (static), inner (non-static), or even more often as anonymous classes inside a JUnit/TestNG test class. There is nothing preventing mock classes from being top-level, though. That would be useful if the mock class is to be reused in multiple test classes. As we'll see later, often the most convenient way to implement mock classes is by making them anonymous and local to an individual test method.

A new mock class is created when we have a "real class" defined in production code which needs to be mocked for a given test. It should define at least one mock method, with any number of additional methods and constructors; it can also define any number of fields.

Each @Mock method must have a corresponding "real method/constructor" with the same signature in the targeted real class. For a mocked method, the signature consists of the method name and parameters; for a mocked constructor, it's just the parameters, with the mock method having the special name "$init". If a matching real method/constructor cannot be found for a given mock method, either in the specified real class or in its super-classes (excluding java.lang.Object), an IllegalArgumentException is thrown when the test attempts to apply the mock class. Notice this exception can be caused by a refactoring in the real class (such as renaming the real method), so it's important to understand why it happens.

Finally, notice there is no need to have mocks for all methods and constructors in a real class. Any such method or constructor for which no corresponding mock exists in the mock class will simply stay "as is", that is, it won't be mocked. This, of course, assuming there isn't some other mock class applied by the same test to the same real class, something which is perfectly valid (and sometimes useful). If two (or more) mock classes happen to be applied to the same real class for the same test, then such mock classes should avoid defining the same mock twice. In case a mock is duplicated, though, the "last" one to be applied wins.

Setting up mocks for a test

A given mock class must be applied to a corresponding real class to have any effect. We call this "setting up" the mock class. This is usually done inside an individual test method or inside a @BeforeMethod (TestNG) or @Before (JUnit 4) method. Once a mock class is set up, all executions of the mocked methods and constructors of the real class get automatically redirected to the corresponding mock methods.

To set up the MockLoginContext mock class above, we simply instantiate it:

   @Test
   public void settingUpAMockClass() throws Exception
   {
      new MockLoginContext());

      // Inside an application class which creates a suitable CallbackHandler:
      new LoginContext("test", callbackHandler).login();

      ...
   }

Since the mock class is set up inside a test method, the mocking of LoginContext by MockLoginContext will be in effect only for that particular test.

When the constructor invocation that instantiates LoginContext executes, the corresponding "$init" mock method in MockLoginContext will be executed, asserting the validity of the invocation arguments. Similarly, when the LoginContext#login method is called, the corresponding mock method will be executed, which in this case will do nothing since the method has no parameters and void return type. The mock class instance on which these invocations occur is the one created in the first part of the test.

The (partial) example test above simply verifies that the LoginContext class is instantiated with valid arguments through a particular constructor that takes a context name and a callback handler. If the real class is not instantiated at all, the test would still pass (unless some other condition causes it to fail). The invocation of the login method also has no effect on the outcome of this test, except for the fact that such an invocation will result in the execution of an empty mock method instead of the real method.

Now, what if we wanted to simulate an authentication failure for a different test? The LoginContext#login() method declares that it can throw a LoginException "if the authentication fails", so what we need to do is very simple (using JUnit 4 in this example):

   public static class MockLoginContextThatFailsAuthentication extends MockUp<LoginContext>
   {
      @Mock
      public void $init(String name) {}

      @Mock
      public void login() throws LoginException
      {
         throw new LoginException();
      }
   }

   @Test(expected = LoginException.class)
   public void settingUpAnotherMockClass() throws Exception
   {
      new MockLoginContextThatFailsAuthentication();

      // Inside an application class:
      new LoginContext("test").login();
   }

This test will only pass if the LoginContext#login method throws an exception, which it will when the corresponding mock method is executed.

Kinds of methods which can have mocks

So far, we have only mocked public instance methods with public instance mock methods. In reality, any other kind of method in a real class can be mocked: methods with private, protected or "package-private" accessibility, static methods, final methods, and native methods. (Also synchronized and strictfp methods, but these modifiers only affect the method implementation, not its interface.) Even more, an static method in the real class can be mocked by an instance mock method, and vice-versa (an instance real method with an static mock); the same applies for the final modifier.

Methods to be mocked need to have an implementation, though not necessarily in bytecode (in the case of native methods). Therefore, an abstract method cannot be mocked directly, and the same applies to the methods of a Java interface. (That said, as shown later the Mockups API can automatically create a proxy class that implements an interface.)

In-line mock classes

Typically, an specific group of mock methods for a given real class will only be useful for a single test. In such a situation we can create an anonymous mock class inside an individual test method, as demonstrated by the next example.

   @Test
   public void setingUpMocksUsingAnAnonymousMockClass() throws Exception
   {
      new MockUp<LoginContext>() {
         @Mock void $init(String name) { assertEquals("test", name); }
         @Mock void login() {}
      });

      new LoginContext("test").login();
   }

Note that mock methods don't need to be public.

Mocking interfaces

Most of the time a mock class targets a real class directly. But what if we need a mock object that implements a certain interface, to be passed to code under test? The following example test shows how it is done for the interface javax.security.auth.callback.CallbackHandler.

   @Test
   public void mockingAnInterface() throws Exception
   {
      CallbackHandler callbackHandler = new MockUp<CallbackHandler>() {
         @Mock
         void handle(Callback[] callbacks)
         {
            assertEquals(1, callbacks.length);
            assertTrue(callbacks[0] instanceof NameCallback);
         }
      }.getMockInstance();

      callbackHandler.handle(new Callback[] {new NameCallback("Enter name:")});
   }

The MockUp#getMockInstance() method returns a proxy object that implements the desired interface. This method returns null if the mocked type is not an interface.

Invocation count constraints

All example tests shown so far only used JUnit/TestNG "asserts" to verify invocation arguments. This is the part of the API that extends conventional state-based testing to the data items exchanged between objects. Sometimes, though, we may want to verify if a given method/constructor in a dependency is invoked at all by the unit under test. We may also want to verify exactly how many invocations a given mock received during the execution of a test, or specify that the test should fail if more/less than a certain number of invocations occurs. For this, we can specify declarative constraints on the invocation count of a given mock, as the following example shows.

   @Test
   public void specifyingInvocationCountConstraints() throws Exception
   {
      new MockUp<LoginContext>() {
         @Mock(minInvocations = 1)
         void $init(String name) { assertEquals("test", name); }

         @Mock(invocations = 1)
         void login() {}

         @Mock(maxInvocations = 1)
         void logout() {}
      });

      new LoginContext("test").login();
   }

In this test we used all three attributes of the @Mock annotation related to invocation counts. The first mock specifies that the LoginContext(String) constructor must be invoked at least once during the test. The second one specifies that the login() method must be invoked exactly once, while the third declares that logout() can be invoked, but not more than once.

It is also valid to specify both minInvocations and maxInvocations on the same mock, in order to constrain the invocation count to a given range.

Mocking class initializers

When a class in production code performs some work in one or more static initialization blocks, we often need to mock it out so it doesn't interfere with test execution. We can define an special mock method for that, as shown below.

   @Test
   public void mockingStaticInitializers()
   {
      new MockUp<ClassWithStaticInitializers>() {
         @Mock
         void $clinit()
         {
            // Do something here, usually nothing.
         }
      };

      ClassWithStaticInitializers.doSomething();
   }

Special care must be taken when the static initialization code of a class is mocked out. Note that this includes not only any "static" blocks in the class, but also any assignments to static fields (excluding those resolved at compile time, which do not produce any executable bytecode). Since the JVM only attempts to initialize a class once, restoring the static initialization code of a mocked out class will not have any effect. So, if you mock out the static initialization of a class that hasn't been initialized by the JVM yet, the original class initialization code will never be executed in the test run. This will cause any static fields that are assigned with expressions computed at runtime to instead remain initialized with the default values for their types.

Accessing the invocation context

A mock method can optionally declare an extra parameter of type mockit.Invocation, provided it is the first parameter. For each actual invocation to the corresponding mocked method/constructor, an Invocation argument will be automatically passed in when the mock method is executed.

This invocation context object provides several "getters" which can be used inside the mock method. One is the getInvokedInstance() method, which returns the mocked instance on which the invocation occurred (null if the mocked method is static). Other getters provide the number of invocations (including the current one) to the mocked method/constructor, the invocation count constraints (if any) as specified in the @Mock annotation, etc. Below we have an example test.

   @Test
   public void accessingTheMockedInstanceInMockMethods() throws Exception
   {
      final Subject testSubject = new Subject();

      new MockUp<LoginContext>() {
         @Mock
         void $init(Invocation invocation, String name, Subject subject)
         {
            assertNotNull(name);
            assertSame(testSubject, subject);

            // Gets the invoked instance.
            LoginContext loginContext = invocation.getInvokedInstance();

            // Verifies that this is the first invocation.
            assertEquals(1, invocation.getInvocationCount());

            // Forces setting of private Subject field, since no setter is available.
            Deencapsulation.setField(loginContext, subject);
         }

         @Mock(minInvocations = 1)
         void login(Invocation invocation)
         {
            // Gets the invoked instance.
            LoginContext loginContext = invocation.getInvokedInstance();

            // getSubject() returns null until the subject is authenticated.
            assertNull(loginContext.getSubject());

            // Private field set to true when login succeeds.
            Deencapsulation.setField(loginContext, "loginSucceeded", true);
         }

         @Mock
         void logout(Invocation invocation)
         {
            // Gets the invoked instance.
            LoginContext loginContext = invocation.getInvokedInstance();

            assertSame(testSubject, loginContext.getSubject());
         }
      };

      LoginContext theMockedInstance = new LoginContext("test", testSubject);
      theMockedInstance.login();
      theMockedInstance.logout();
   }

Reentrant mocks

A reentrant method is one which can be re-entered once it is already executing. A recursive method is an example. A reentrant mock method is similar, but instead of causing itself to be re-entered, it causes the corresponding mocked method to be re-entered; and when this happens the original code of the real method is executed, without going through the mock implementation again. This is done with a call to the Invocation#proceed(...) method, on the Invocation object received as the first parameter to the mock method.

The example test below exercises a LoginContext object created normally (without any mocking in effect at creation time), using an unspecified configuration. (For the complete version of the test, see the mockit.MockAnnotationsTest class.)

   @Test
   public void reenterMockedMethods() throws Exception
   {
      // Create objects to be exercised by the code under test:
      LoginContext loginContext = new LoginContext("test", null, null, configuration);

      // Set up mocks:
      ReentrantMockLoginContext mockInstance = new ReentrantMockLoginContext();

      // Exercise the code under test:
      assertNull(loginContext.getSubject());
      loginContext.login();
      assertNotNull(loginContext.getSubject());
      assertTrue(mockInstance.loggedIn);

      mockInstance.ignoreLogout = true;
      loginContext.logout(); // first entry, won't re-enter
      assertTrue(mockInstance.loggedIn);

      mockInstance.ignoreLogout = false;
      loginContext.logout(); // second entry, will re-enter
      assertFalse(mockInstance.loggedIn);
   }

   static final class ReentrantMockLoginContext extends MockUp<LoginContext>
   {
      boolean ignoreLogout;
      boolean loggedIn;

      @Mock
      void login(Invocation inv) throws LoginException
      {
         try {
            inv.proceed(); // re-enters the mocked method, executing the real code
            loggedIn = true;
         }
         finally {
            // This is here just to show that arbitrary actions can be taken inside
            // the mock, before and/or after the real method gets executed.
            LoginContext lc = inv.getInvokedInstance();
            System.out.println("Login attempted for " + lc.getSubject());
         }
      }

      @Mock
      void logout(Invocation inv) throws LoginException
      {
         // We can choose to re-enter the mocked method or not.
         if (!ignoreLogout) {
            inv.proceed();
            loggedIn = false;
         }
      }
   }

In the example above, all real code exercised by the test actually gets executed, even though some methods (login and logout) are mocked. The example indeed is contrived; in practice, reentrant mocks would not normally be useful for testing per se, not directly at least. Although not shown here, this can be also be done inside a "$init(Invocation inv, ...)" mock method, to execute the real implementation of a mocked constructor.

You may have noticed that a reentrant mock effectively behaves like advice (from AOP jargon) for the corresponding real method. This is a powerful ability that can be useful for certain things. JMockit itself uses mock classes containing reentrant mocks to provide integration with the JUnit and TestNG test runners.

For more details on all the methods available in the mockit.Invocation class, see its API documentation.

Reusing mocks between tests

Most tests will probably only use dedicated mock classes, specifically created for each particular test. There will be times, though, when the same mock class can be reused by multiple test methods, either in a single test class or across the entire test suite. We will now see different ways to set up mocks so they are shared by a whole group of tests, as well as ways to define reusable mock classes.

Using before/after methods

In a given test class, we can define instance methods that will run before and after each test method (even when the test throws an error or exception). With JUnit 3.8-style tests, these are the setUp() and tearDown() method overrides. In JUnit 4.x we use the @Before and @After annotations on one or more arbitrary instance methods of the test class. The same applies to the @BeforeMethod and @AfterMethod annotations of TestNG.

Any mock class that can be applied from inside a test method can also be applied from a "before" method, using one of the "set-up" methods or by instantiating a mock-up class. Such a mock class will remain in effect for the execution of all test methods in the test class. The only difference of applying mocks in a "before" method is that they also remain in effect inside "after" methods, if any.

For example, if we wanted to mock the LoginContext class with a mock-up class for a bunch of related tests, we would have the following methods in the test class:

public class MyTestClass
{
   @Before
   public void setUpSharedMocks()
   {
      new MockUp<LoginContext>() {
         // shared mocks here...
      };
   }

   // test methods that will share the mocks set up above...
}

The example above uses JUnit 4, but the equivalent code for TestNG or with JUnit 3.8-style tests would be practically the same.

It is also valid to extend from base test classes, which may optionally define "before" and/or "after" methods containing calls to the Mockups API.

Reusable mock classes

Named mock classes can be designed as concrete (and optionally final) classes that are then used in specific tests. When instantiated directly by test code, such mock instances can be configured through constructor arguments, fields, or non-mock methods. Alternatively, they can be designed as base classes (possibly abstract) to be extended by concrete mock classes inside specific test classes or methods.

The example tests for this section come from JMockit's own test suite. They exercise the following class, partially reproduced here:

public final class TextFile
{
   // fields and constructors that accept a TextReader or DefaultTextReader object...
   
   public List<String[]> parse()
   {
      skipHeader();

      List<String[]> result = new ArrayList<String[]>();

      while(true) {
         String  strLine = nextLine();

         if (strLine == null) {
            closeReader();
            break;
         }

         String[] parsedLine = strLine.split(",");
         result.add(parsedLine);
      }

      return result;
   }

   // private helper methods that call "skip(n)", "readLine()", and "close()"...
   
   public interface TextReader
   {
      long skip(long n) throws IOException;
      String readLine() throws IOException;
      void close() throws IOException;
   }

   static final class DefaultTextReader implements TextReader
   {
      DefaultTextReader(String fileName) throws FileNotFoundException { ...mocked... }
      public long skip(long n) throws IOException { ...mocked... }
      public String readLine() throws IOException { ...mocked... }
      public void close() throws IOException { ...mocked... }
   }
}

Some of the tests for the class above are as follows.

public final class TextFileUsingMockUpsTest
{
   // A reusable mock-up class to be applied in specific tests.
   static final class MockTextReaderConstructor extends MockUp<DefaultTextReader>
   {
      @Mock(invocations = 1)
      void $init(String fileName) { assertThat(fileName, equalTo("file")); }
   }

   @Test
   public void parseTextFileUsingDefaultTextReader() throws Exception
   {
      new MockTextReaderConstructor();
      new MockTextReaderForParse<DefaultTextReader>() {};

      List<String[]> result = new TextFile("file", 200).parse();

      // assert result from parsing
   }

   ...

The test above uses two reusable mock classes. The first one encapsulates a mock for the single constructor of the TextFile.DefaultTextReader nested class. Any tests exercising code in the TextFile class that invokes this constructor will therefore use this mock class. It is applied by simply being instantiated inside the test method.

The second mock class used by the test targets the same DefaultTextReader class. As we see next, it defines mocks for a whole different set of members, which happen to be the methods called from the TextFile#parse() method.

   ...

   // A reusable base mock class to be extended in specific tests.
   static class MockTextReaderForParse<T extends TextReader> extends MockUp<T>
   {
      static final String[] LINES = { "line1", "another,line", null};
      int invocation;

      @Mock(invocations = 1)
      long skip(long n)
      {
         assertEquals(200, n);
         return n;
      }

      @Mock(invocations = 3)
      String readLine() throws IOException { return LINES[invocation++]; }

      @Mock(invocations = 1)
      void close() {}
   }

   ...

The mock-up class above, like the mockit.MockUp<T> class which it extends, is generic. In this particular case, this is necessary because the tested TextFile class works with two different types for the "text reader" dependency: TextFile.TextReader (an interface which client code can implement), and TextFile.DefaultTextReader (an internal default implementation of the interface). The previous test simply used this mock class as is, by defining an anonymous subclass which specifies the type to be mocked as the DefaultTextReader concrete class. The next test, on the other hand, passes a TextReader implementation to TextFile:

   ...

   @Test
   public void parseTextFileUsingProvidedTextReader() throws Exception
   {
      TextReader textReader = new MockTextReaderForParse<TextReader>() {}.getMockInstance();

      List<String[]> result = new TextFile(textReader, 200).parse();

      // assert result from parsing
   }

   ...

The interface implementation, in this case, is a mock proxy object obtained through the MockUp<T>#getMockInstance() method.

Finally, we get to a more interesting case, where the concrete mock subclass actually overrides some of the mocks inherited from the base mock class:

   ...

   @Test
   public void doesNotCloseTextReaderInCaseOfIOFailure() throws Exception
   {
      new MockTextReaderConstructor();
   
      new MockTextReaderForParse<DefaultTextReader>() {
         @Override @Mock
         String readLine() throws IOException { throw new IOException(); }

         @Override @Mock(invocations = 0)
         void close() {}
      };

      TextFile textFile = new TextFile("file", 200);

      try {
         textFile.parse();
         fail();
      }
      catch (RuntimeException e) {
         assertTrue(e.getCause() instanceof IOException);
      }
   }
}

The test forces an IOException to be thrown in the first call to readLine(). (This exception will get wrapped in a RuntimeException by the parse method.) It also specifies, through an invocation count constraint, that the close() method should never be called. This shows that not only the behavior of the inherited mock is overridden, but also any of the metadata specified through the @Mock annotation.