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.
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.
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.
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.)
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.
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.
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.
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.
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();
}
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.
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.
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.
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.