In the JMockit toolkit, the Mockups API provides support for the creation of state-based tests and for the creation of fake implementations. In this kind of testing, the focus is not on the interactions between a tested class and its dependencies, but on the data 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 is using them. As for fake implementations, they can be particularly useful in integration tests, where tested classes with dependencies on external entities (the network, the file system, etc.) can be transparently isolated from the implementations of those "problematic" dependencies.
Regular state verification, performed after exercising the unit under test, is typically done through JUnit/TestNG assertions written at the end of each test method. The Mockups API extends the ability of a test to do state verification, by allowing those same assertions to be executed whenever a dependency is invoked from inside the tested code. As the tested unit passes data to dependencies through method and constructor invocations, the corresponding mock implementations specified for those dependencies get called. These implementations (usually defined inside test classes) will then execute the appropriate assertions on the received invocation arguments. In addition, the API allows the test to verify if any given method or constructor of a mocked dependency is called at all, or more generally to verify the actual number of invocations against an specified exact, minimum, or maximum number of invocations.
For the remaining of this chapter, lets say we want to write unit 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 the mockit.Mock annotation.
A mock class is any class that is annotated with mockit.MockClass, that extends
the mockit.MockUp<T> generic base class, or that is explicitly passed to one of several
mock-setup methods.
For short, in this chapter we will refer to such annotated mock methods simply as "mocks" (in other contexts, the
word "mock" may refer to an instance of a mocked class instead).
The example below shows several mocks defined in a named mock class.
public static class MockLoginContext
{
@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/redefined 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 classes inside a JUnit/TestNG test class.
That's why the example mock class above is declared to be static; it could also have been an inner
(non-static) 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, though, 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 normally done for a specific test or set of tests.
In the first case, it occurs inside individual test methods.
In the second one, a @BeforeMethod (TestNG), setUp() (in JUnit 3.8-style
tests), or @Before (JUnit 4) method can be used.
During the time a mock class is in effect, the methods and constructors of the real class which have corresponding
mocks will stay mocked, with any calls made on them automatically and transparently redirected to the corresponding
mocks for execution.
There are a few different ways to set up mock classes, each with a different way to specify the corresponding real
class, as we'll now see.
For the MockLoginContext mock class above, the corresponding real class is
javax.security.auth.login.LoginContext.
To set it up for an individual test, we could write the following (assuming the necessary imports):
@Test
public void setUpMocksForGivenRealClassWithGivenMockInstance() throws Exception
{
Mockit.setUpMock(LoginContext.class, new MockLoginContext());
// Inside an application class which creates a suitable CallbackHandler:
new LoginContext("test", callbackHandler).login();
...
}
Since the call above to Mockit.setUpMock occurs inside a test method, the mocking of
LoginContext by MockLoginContext will be in effect only for this
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.
Notice the test instantiates the mock class and passes this instance in the call to
Mockit.setUpMock(Class> realClass, Object mock).
This mock instance will be the one on which any mock method (such as login) is called.
The (partial) example test above would simply verify that the LoginContext class is
instantiated with valid arguments when one particular constructor that takes a context name and a callback handler is
used.
If the 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
{
@Mock
public void $init(String name) {}
@Mock
public void login() throws LoginException
{
throw new LoginException();
}
}
@Test(expected = LoginException.class)
public void setUpMocksForGivenRealClassWithoutPrecreatedMockInstance() throws Exception
{
Mockit.setUpMock(LoginContext.class, MockLoginContextThatFailsAuthentication.class);
// 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.
Notice that in this case we passed the class literal for the mock class instead of an instance.
This will cause a new instance of the mock class to be created automatically every time a
non-static mock method needs to be called (which happens whenever a mocked method on the real class is
called).
The mock method will be called on this new instance, which will then be discarded (that is, it won't be reused later
for new invocations).
Naturally, mock methods that are static are never called on any instance, so no new instance is created
for them.
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 a named mock class, mocks usually need to be declared as public, otherwise an
IllegalAccessError might get thrown.
@MockClass annotation
The Mockit.setUpMock method, in all of its two-parameter variations, requires the real class to be
explicitly provided through an argument.
The Mockit.setUpMocks
method, in contrast, takes only mock class literals and/or mock class instances.
The applicable real class is specified through the mockit.MockClass annotation,
applied on the mock class itself, as the following example shows.
@MockClass(realClass = LoginContext.class)
public static class MockLoginContext
{
// @Mocks here...
}
@MockClass(realClass = SomethingElse.class)
public final class MockSomethingElse
{
// @Mocks here...
}
@Test
public void setUpMocksForMultipleClasses()
{
Mockit.setUpMocks(MockLoginContext.class, new MockSomethingElse());
// Calls code under test that uses the "LoginContext" and "SomethingElse" classes:
...
}
The realClass attribute is mandatory for @MockClass.
A mock class is typically used for the same real class every time.
Therefore, nothing more natural than associating said class directly with the mock class, avoiding the need to
specify it every time the mocks are set up.
Named mock classes, typically defined as nested or inner classes of the test class, definitely have their place. Often, however, an specific group of mocks for a given real class will be useful for a single test. In such a situation we can create anonymous mock classes inside individual test methods, as demonstrated by the next example.
@Test
public void setUpMocksUsingAnonymousMockClass() throws Exception
{
Mockit.setUpMock(LoginContext.class, new Object() {
@Mock void $init(String name) { assertEquals("test", name); }
@Mock void login() {}
});
new LoginContext("test").login();
}
An anonymous mock class is always instantiated at the place of definition and cannot be annotated with
@MockClass, so we need a mock setup mechanism that takes both the real class and the
mock instance.
A more elegant alternative to the use of Mockit.setUpMock with an "in-line mock class" is shown next,
using the mockit.MockUp<T> generic base class.
@Test
public void setUpMocksUsingLocalMockUpClass() throws Exception
{
new MockUp<LoginContext>() {
@Mock void $init(String name) { assertEquals("test", name); }
@Mock void login() {}
};
new LoginContext("test").login();
}
In this case, we still have an anonymous mock class, but it is created as a subclass of
MockUp<T> where the real class is specified through the generic
type parameter T.
(Subclasses of MockUp can also be named, of course.)
Given this succinct and readable syntax, and the fact that mock classes tend to be specific to individual tests, the
creation of such mock-up classes should be the most common way to define mocks with the Mockups API.
As the previous example tests have shown, it is perfectly safe to declare mock methods in an anonymous mock class as
non-public.
This is an added bonus over annotated (and named) mock classes, which often (but not always) need to be public with
their mock methods also 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 two following example tests show how it is done, one using an annotated mock class, the other a mock-up class. The mocked interface is javax.security.auth.callback.CallbackHandler.
@Test
public void setUpMocksForInterface() throws Exception
{
CallbackHandler callbackHandler = Mockit.setUpMock(new MockCallbackHandler());
callbackHandler.handle(new Callback[] {new NameCallback("Enter name:")});
}
@MockClass(realClass = CallbackHandler.class)
public static class MockCallbackHandler
{
@Mock
public void handle(Callback[] callbacks)
{
assertEquals(1, callbacks.length);
assertTrue(callbacks[0] instanceof NameCallback);
}
}
@Test
public void mockInterfaceWithMockUp() 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 Mockit.setUpMock(Object)
method returns a proxy object that implements the desired interface, when the mock class
specifies that interface as the value of the realClass attribute (if an actual class
is specified in the attribute, the method will apply the mock class and return null).
The method accepts either an instance of the annotated mock class or the Class
literal for the same.
Again, a more elegant alternative is obtained with the use of a mock-up class, which provides
the proxy instance through a call to the getMockInstance() method.
This method returns null if the mocked type is not an interface.
Notice that for the first test the mock class and its mock methods are declared to be
public and must be so to avoid an IllegalAccessError, while in the
second test the mock-up class and the mock method don't have to be public (an anonymous
class is always non-public anyway).
Sometimes all we need is to stub out the executable code in an entire class or in selected methods/constructors.
When a method is stubbed out, it does nothing when executed.
An stubbed out constructor will also do nothing, except for calling the necessary constructor in the super-class.
Methods with non-void return type will simply return the default value for that type: zero for numeric
primitive types, false for boolean, an empty array or collection, or null for
any other reference type (including String).
Special care must be taken when the static initialization code of a class is stubbed 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 an
stubbed out class will not have any effect.
So, if you stub 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 (except those assigned with compile-time constants) to retain their initial default
values.
The Mockit class provides a set of "stubbing" methods with names starting in
"stubOut".
They accept one or more classes to be stubbed out, with some of them also accepting a set of filters for methods
and constructors.
The static initializer of a class is specified by the special filter value "<clinit>".
For details and examples, see the relevant API
documentation.
The @MockClass annotation also gives us the ability to specify methods and/or
constructors in the real class that should be stubbed out so they have no effect when
called during a test.
Stubbing out a method achieves the same result as mocking it with a mock method that does nothing, except for
returning the default value according to the method return type when it's not void.
The following example tests make use of this feature.
@Test
public void mockAndStubOutClassAccordingToSpecifiedStubs() throws Exception
{
Mockit.setUpMocks(new MockLoginContextWithStubs());
LoginContext context = new LoginContext("");
context.login();
context.logout();
}
@MockClass(realClass = LoginContext.class, stubs = {"(String)", "logout"})
final class MockLoginContextWithStubs
{
@Mock
void login() {} // this could also have been an stub
}
@Test
public void mockAndStubOutClassAccordingToSpecifiedInverseStubs() throws Exception
{
Mockit.setUpMocks(MockLoginContextWithInverseStubs.class);
LoginContext context = new LoginContext("", null, null);
context.login();
context.logout();
}
@MockClass(realClass = LoginContext.class, stubs = "", inverse = true)
static class MockLoginContextWithInverseStubs
{
@Mock
static void login() {}
}
In the first test above, the constructor of the real class (LoginContext) which has
one parameter of type String is stubbed out, as well as all methods in the same
class with name logout. Parameters are ignored if not specified in the stub filter,
so all overloads (in case there is more than one) with the same method name will be stubbed out.
The second mock class, MockLoginContextWithInverseStubs, uses the
inverse attribute (which by default is false) to specify that all
methods and constructors not matching the specified stub filters will be stubbed out.
In this example, the list of stub filters contains only an empty filter which matches nothing, so everything in the
real class is stubbed out. Any mocks defined in the mock class will override the stub, however.
Each stub filter string specified with the stubs attribute can match any number of
methods of the real class, since the initial part actually is a regular expression for method names.
For constructors, this part is left empty since they have no names.
The second part in a stub filter string, which can be empty for methods but never for
constructors, is a comma-separated list of parameter type names, between parentheses.
Each parameter type name either is the name of a primitive type, or a fully qualified reference type name.
For convenience, it can be a unique suffix for such type names instead; this makes it
easy to specify reference type names, since in most cases the package name can be omitted.
Finally, as seen in the example above, the empty stub filter will match no methods or
constructors in the real class, or all methods and constructors if used with inverse = true.
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 specifyInvocationCountConstraints() throws Exception
{
Mockit.setUpMock(LoginContext.class, new Object()
{
@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 the test above we used an anonymous mock class with the setUpMock method,
although a nicer solution could be obtained with a local MockUp object.)
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 stub it out so it doesn't interfere with test execution.
To do just that for a given real class, we can write something like
@MockClass(realClass = MyRealClass.class, stubs = "<clinit>").
If we instead want some test code to execute when the static initialization of the real class
gets executed by the JVM, then we can define an special mock, as shown below.
@Test
public void mockStaticInitializer()
{
new MockUp<ClassWithStaticInitializers>()
{
@Mock(invocations = 1)
void $clinit()
{
// Do something here.
}
};
ClassWithStaticInitializers.doSomething();
}
We can even verify that static initialization occurs, as specified by the invocation count constraint above. Notice however that once loaded by the JVM, a class will be initialized no more than once, so the test would fail if it runs after the class has already been initialized.
We have already seen two different situations when mock classes get instantiated:
new operator, and passed to JMockit in
a call to a Mockit.setUpMock or Mockit.setUpMocks method, or alternatively as a
MockUp subclass instance.
In this case the same mock instance is used for all calls to instance mock methods, as long as the mock class
remains in effect.
Sometimes it may be important to have more control on when exactly a mock class is automatically instantiated by
JMockit.
There is also a situation which is not covered by the previously described instantiation mechanisms, and which may be
needed or useful for certain test cases: instantiation per mocked instance.
To cover such needs the @MockClass annotation has an instantiation
attribute, whose values are given by the
Instantiation "enum".
The default value for this attribute is Instantiation.PerMockInvocation.
The following sub-sections describe each of the possible instantiation modes which can be used to control how and when JMockit creates automatic mock class instances. Notice this is applicable only to situation 2 above.
For a mock class annotated as
@MockClass(realClass = ..., instantiation = PerMockSetup),
a call like Mockit.setUpMock(realClass, MyMockClass.class) or
Mockit.setUpMocks(MyMockClass.class) will result in the creation of the mock
instance at the time of the call.
This single mock instance will then be used for all instance method invocations, for as long as
the mock class remains in effect.
Notice this is equivalent to passing an instance created by test code in one of those mock setup calls.
For a mock class annotated as
@MockClass(realClass = ..., instantiation = PerMockInvocation),
a call like Mockit.setUpMock(realClass, MyMockClass.class) or
Mockit.setUpMocks(MyMockClass.class) will not result in the creation of any
mock instance at this time, but will later if and when an instance mock method needs to be invoked.
For each such invocation, a new mock instance is created and then discarded as soon as the mock call terminates.
Notice this is the default JMockit behavior when a class literal for the mock class is passed in
a mock setup call, and the instantiation attribute isn't specified.
For a mock class annotated as
@MockClass(realClass = ..., instantiation = PerMockedInstance),
a call like Mockit.setUpMock(realClass, MyMockClass.class) or
Mockit.setUpMocks(MyMockClass.class) will result in the on-demand creation
of one new mock instance for each and every new mocked instance (ie, an instance of the real class).
In other words, once the mock class is in effect, whenever a mocked instance method of the real
class is invoked, a paired instance of the mock class will be created if none yet exists for that
particular instance of the real class; this same mock instance will then be used for all
invocations to mock instance methods.
The ability to have a mock class instance for each real class instance will normally only be useful if the mock class stores some state that is specific to each instance of the real class.
Notice the mock object lifetime described above can only be achieved by using this particular instantiation mode.
As we have seen, there is a relationship between mocked instances (that is, instances of real classes which have been mocked) and mock instances (instances of mock classes). Mock instances can be created explicitly in test code, or automatically by JMockit according to the instantiation mode of the mock class. Mocked instances are created inside tested code, or in test code so they can be passed to tested code.
For certain use cases, a mock method executing on a mock instance may need access to the
corresponding mocked instance.
This can be achieved by declaring an instance field of name it in
the mock class, with the type of the mocked real class.
Every time a mocked instance method is redirected to execute the corresponding mock instance
method, the it field on the mock instance will be set to the mocked instance.
If a mocked constructor has a corresponding $init mock method, then the
field will be set to the newly created (and uninitialized) instance.
In practice, the use of an it field by itself is uncommon, so the following example
test is a contrived (although interesting) one. The next section describes a more likely use for such fields.
@Test
public void accessMockedInstanceThroughItField() throws Exception
{
final Subject testSubject = new Subject();
new MockUp<LoginContext>()
{
LoginContext it;
@Mock(invocations = 1)
void $init(String name, Subject subject)
{
assertNotNull(name);
assertSame(testSubject, subject);
// Forces setting of private Subject field, since no setter is available.
setField(it, subject);
}
@Mock(invocations = 1)
void login()
{
// getSubject() returns null until the subject is authenticated.
assertNull(it.getSubject());
// Private field set to true when login succeeds.
setField(it, "loginSucceeded", true);
}
@Mock(invocations = 1)
void logout()
{
assertSame(testSubject, it.getSubject());
}
};
LoginContext theMockedInstance = new LoginContext("test", testSubject);
theMockedInstance.login();
theMockedInstance.logout();
}
The example above (which is an actual test in JMockit's own test suite) used a mock-up class, but
annotated and regular anonymous mock classes can also declare an it field.
The setField methods used in this test are statically imported from the
mockit.Deencapsulation utility class, and are only used here to make the test more
meaningful.
The local variable theMockedInstance contains the instance which the it
field will be set to on the mock-up instance, for each of the three mock invocations in this particular test.
Usually, a reentrant method is one which can be re-entered once it is already executing. Recursive methods, for example, are typically reentrant. 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 again.
If the mock is an instance method mocking a real instance method, it will need access to the
mocked instance in order to call back the mocked instance method.
For this, the mock class will need to declare an appropriate it field.
A mocked method which has a reentrant mock will have different behavior than if it had a regular
mock, so we use the @Mock(reentrant = true) annotation
attribute to tell JMockit about our intentions.
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 reenterMockedMethodsThroughItField() 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>
{
LoginContext it;
boolean ignoreLogout;
boolean loggedIn;
@Mock(reentrant = true)
void login() throws LoginException
{
try {
it.login(); // 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.
System.out.println("Login attempted for " + it.getSubject());
}
}
@Mock(reentrant = true)
void logout() throws LoginException
{
// A reentrant mock execution can re-enter the mocked method or not,
// based on a condition.
if (!ignoreLogout) {
it.logout();
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.
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.
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...
}
If we had an annotated mock class instead of a mock-up, the set up would be done with a
Mockit.setUpMocks(NamedMockClass.class) call or with a Mockit.setUpMock variant.
The example above uses JUnit 4, but the equivalent code for TestNG or with JUnit 3.8-style tests would be almost 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 of all kinds (annotated with @MockClass or not, mock-up 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 directly instantiated 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.
And since it is a mock-up class, it can be applied through simple instantiation 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.