In the JMockit toolkit, the Mockups API provides support for the creation of fake implementations, or "mock-ups". Fakes are different from the mocking API in that, rather than specifying in a test the invocations we expect a dependency will receive when used by code under test, we modify the implementation of the dependency so that it suits the needs of the test. Typically, a mock-up targets a few methods and/or constructors in the class to be faked, while leaving most other methods and constructors unmodified.
Fake implementations 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 code 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 fake ones is completely 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.
Therefore, an application class depending on LoginContext
will be the "code 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, a static
method in the real class can be mocked by an instance mock method, and
vice-versa (an instance real method with a 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, a 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 settingUpMocksUsingAnAnonymousMockClass() 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.
To demonstrate this feature, lets consider the following code under test.
public interface Service { int doSomething(); }
final class ServiceImpl implements Service { public int doSomething() { return 1; } }
public final class TestedUnit
{
private final Service service1 = new ServiceImpl();
private final Service service2 = new Service() { public int doSomething() { return 2; } };
public int businessOperation()
{
return service1.doSomething() + service2.doSomething();
}
}
The method we want to test, businessOperation()
, uses classes that implement a separate interface,
Service
.
One of these implementations is defined through an anonymous inner class, which is completely inaccessible (except
for the use of Reflection) from client code.
Given a base type (be it an interface
, an abstract
class, or any sort of base class), we
can write a test which only knows about the base type but where all implementing/extending implementation classes get
mocked.
To do so, we create a mock-up whose mocked type refers only to the known base type, and does so through a
type variable.
Not only will implementation classes already loaded by the JVM get mocked, but also any additional classes that
happen to get loaded by the JVM during later test execution.
This ability is demonstrated below.
@Test
public <T extends Service> void mockingImplementationClassesFromAGivenBaseType()
{
new MockUp<T>() {
@Mock int doSomething() { return 7; }
};
int result = new TestedUnit().businessOperation();
assertEquals(14, result);
}
In the test above, all invocations to methods implementing Service#doSomething()
will be redirected to
the mock method implementation, regardless of the actual class implementing the interface method.
All example tests shown so far only used JUnit/TestNG "asserts" to verify invocation arguments. Sometimes, though, we may want to verify if a given method/constructor in a dependency is invoked at all by the code 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 may need to mock it out so it doesn't interfere with test execution. We can define a special mock method for that, as shown below.
@Test
public void mockingStaticInitializers()
{
new MockUp<ClassWithStaticInitializers>() {
@Mock
void $clinit()
{
// Do nothing here (usually).
}
};
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 executable bytecode).
Since the JVM only attempts to initialize a class once, restoring the static initialization code of a
mocked out class will have no 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
object 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();
}
Once a @Mock
method is executing, any additional calls to the corresponding mocked
method are also redirected to the mock method, causing its implementation to be re-entered.
If, however, we want to execute the real implementation of the mocked method, we can call the 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 proceedIntoRealImplementationsOfMockedMethods() throws Exception
{
// Create objects to be exercised by the code under test:
LoginContext loginContext = new LoginContext("test", null, null, configuration);
// Set up mocks:
ProceedingMockLoginContext mockInstance = new ProceedingMockLoginContext();
// Exercise the code under test:
assertNull(loginContext.getSubject());
loginContext.login();
assertNotNull(loginContext.getSubject());
assertTrue(mockInstance.loggedIn);
mockInstance.ignoreLogout = true;
loginContext.logout(); // first entry: do nothing
assertTrue(mockInstance.loggedIn);
mockInstance.ignoreLogout = false;
loginContext.logout(); // second entry: execute real implementation
assertFalse(mockInstance.loggedIn);
}
static final class ProceedingMockLoginContext extends MockUp<LoginContext>
{
boolean ignoreLogout;
boolean loggedIn;
@Mock
void login(Invocation inv) throws LoginException
{
try {
inv.proceed(); // executes the real code of the mocked method
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 proceed into the real implementation or not.
if (!ignoreLogout) {
inv.proceed();
loggedIn = false;
}
}
}
In the example above, all the code inside the tested LoginContext
class will get
executed, even though some methods (login
and logout
) are mocked.
This example is contrived; in practice, the ability to proceed into real implementations would not normally be useful
for testing per se, not directly at least.
You may have noticed that use of Invocation#proceed(...)
in a mock method effectively behaves like
advice (from AOP jargon) for the corresponding real method.
This is a powerful ability that can be useful for certain things (think of an interceptor or decorator).
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, 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 when using TestNG.
Any mock class that can be applied from inside a test method can also be applied from a "before" method, 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, but the equivalent code for TestNG 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.
As we have seen, mock-up classes are usually applied for the duration of an individual test.
There may be times when we want to mock classes for the entire scope of a test class (that is, for all of its test
methods), or for the entire scope of a test suite (that is, for all of its test classes).
It is also possible to specify mock classes for an entire test run through external configuration, by setting a
JVM-wide system property or adding a jmockit.properties
file to the runtime classpath.
To have a mock-up class cover the entire scope (all tests) of a given test class, we simply apply it inside a
@BeforeClass
method (with JUnit or TestNG).
To apply mock-ups over a test suite, we can use a TestNG @BeforeSuite
method, or a JUnit Suite
class.
The next example shows a JUnit 4 test suite configuration with the application of mock-ups.
@RunWith(Suite.class)
@Suite.SuiteClasses({MyFirstTest.class, MySecondTest.class})
public final class TestSuite
{
@BeforeClass
public static void setupMocks()
{
new LoggingMocks();
new MockUp<SomeClass>() {
@Mock someMethod() {}
};
}
}
In this example, we apply the LoggingMocks
mock class and an inline mock-up class; their
mocks will be in effect until just after the last test in the test suite has been executed.
The jmockit-mocks
system property supports a comma-separated list of fully qualified
mock-up class names.
If specified at JVM startup time, any such class (which must extend MockUp<T>
) will be
automatically applied for the whole test run.
The mocks defined in startup mock classes will remain in effect until the end of the test run, for all test classes.
Note that a system property can be passed to the JVM through the standard "-D
" command line parameter.
Ant/Maven/etc. build scripts have their own ways of specifying system properties, so check their documentation for
details.
jmockit.properties
file
Mock-up classes specified in the jmockit-mocks
system property can alternatively be specified in a
separate jmockit.properties
file, which should be present at the root of the classpath.
If there are multiple such files in the classpath (either inside jars or plain directories), all class names listed
are added together.
This allows the creation of reusable mock classes which can be packaged in a jar file with its own properties file;
when added to the execution classpath of a test suite, the mock classes will be automatically applied at startup.
For convenience, the entry in the properties file can be written without the "jmockit-
" prefix.