Mockito: Why You Still Should Appreciate InjectMocks Annotation
Anyone who has used Mockito for mocking and stubbing Java classes, probably is familiar with the InjectMocks-annotation. I seemed a little harsh in an article a few years back about why you should not use @InjectMocks to auto-wire fields, even though I actually consider Mockito to be one of the most brilliant mocking frameworks for unit testing in Java.
Every annotation could use a spotlight now and then — even those who come with safety instructions 😉 So I thought, why not show @InjectMocks
some appreciation instead?
How does this work under the hood? What if we were to implement this logic ourselves, just to see how Mockito designed the nuts ‘n bolts to initialize the class under test (i.e. the one annotated with @InjectMocks
) with all the collaborating mocks up to point when the first test method is invoked?
Consider the following JUnit 5 test which verifies whether a waitress can properly “do something”.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | @ExtendWith (MockitoExtension. class ) public class WaitressTest { @Mock CoffeeMachine coffeeMachine; @Spy Toaster toaster; @InjectMocks Waitress waitress; @Test void should_do_something() { // .. } } |
You’ll see 5 different annotations here:
1. JUnit 5’s @ExtendWith
2. Mockito’s @Mock
3. Mockito’s @Spy
4. Mockito’s @InjectMocks
5. JUnit 5’s @Test
The @ExtendWith
is a means to have JUnit pass control to Mockito when the test runs. Without it, Mockito is left out of the loop and the test blows up because all annotated fields stay null
.
Since @Mock
and @Spy
are the only two annotations actually supported by @InjectMocks
I thought I’d use them both. 😉 Mockito also supports the @Captor
annotation on ArgumentCaptor fields, but we don’t use that here.
What exactly is tested in our @Test
-annotated method is not important either, but beforehand Mockito needs to make sure:
- All
@Mock
and@Spy
-annotated fields (e.g.CoffeeMachine
andToaster
) are initialized as mocks and spies Waitress
is created as a real object — and both collaborators are properly “injected” into it.
Start mocking
Let’s assume the complete test class instance WaitressTest
is passed to Mockito’s MockitoAnnotations.initMocks()
(Remember, in the old days you had to call this method manually in the set-up of the test?) which delegates again to a class which implements the AnnotationnEgine
interface, which can be configured by a plugin or come from Mockito’s global configuration.
1 2 3 4 5 6 7 8 9 | public interface AnnotationEngine { /** * Processes the test instance to configure annotated members. * * @param clazz Class where to extract field information, check implementation for details * @param testInstance Test instance */ void process(Class clazz, Object testInstance); } |
We’ll build up our own ‘simplified’ AnnotationEngine
as we go along.
Process the mocks
We have to scan the test class first for fields with need to be mocked: those are annotated with @Mock
, @Spy
and @Captor
.
In reality Mockito processes the @Mock
and @Captor
annotations first, followed by the @Spy
fields.
The generic mechanism uses reflection heavily: walk the fields of the test class, test each field whether to correct annotation is present and handle accordingly.
Mock
Let’s take @Mock
first:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | // import java.lang.reflect.Field; public void process(Class clazz, Object testInstance) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Mock. class )) { // 1 field.setAccessible( true ); // 2 Class type = field.getType(); Object mock = Mockito.mock(type); // 3 field.set(testInstance, mock); // 4 } } } |
What happens?
- See if a field has been annotated with the annotation we want to deal with. In reality, Mockito would also check here for unexpected combinations of multiple annotations on the same field.
- The
@Mock
-annotated field (e.g.CoffeeMachine coffeeMachine
in this case) could beprivate
and yield an exception when we try to update it in step 4, so we have to (try to) make it accessible first. - Based on the type of the field we delegate to another part of the public Mockito API:
Mockito.mock()
— just as if you had invoked this manually in your test. This does the heavy lifting of creating a mock, and returns it as genericObject
. - The new mock object is set as the new value of the field.
In reality, in step 3 Mockito would not just call mock(type)
but uses the overloaded version which takes also the global MockitoSettings
into account, combined with the settings on the annotation itself e.g.
1 2 | @Mock (name = "nespresso" , stubOnly = true , /*...*/ ) CoffeeMachine coffeeMachine; |
Also in reality, every call with the Reflection API (i.e. methods on java.lang.reflect.Field
) could yield a plethora of exceptions (SecurityException
, IllegalAccessException
, IllegalArgumentException
etc) which are dealt with by Mockito and wrapped in a MockitoException
explaining what’s happening.
Captor
Processing the argument captors happens almost the same.
Spot the difference:
1 2 3 4 5 6 | if (field.isAnnotationPresent(Captor. class )) { field.setAccessible( true ); Class type = field.getType(); Object mock = ArgumentCaptor.forClass(type); field.set(testInstance, mock); } |
No surprises there. ArgumentCaptor.forClass
is a public static factory-method present in Mockito before there was a @Captor
annotation 🙂
In reality Mockito additionally first checks whether the field’s type is of type ArgumentCaptor
to provide a better error message in case of a wrong type. In contrast to the other annotations, this @Captor
annotation works only on ArgumentCaptor
types e.g.
1 2 | @Captor ArgumentCaptor sugarCaptor; |
Spy
Last but not least of the mocks, Spy-fields are initialised:
01 02 03 04 05 06 07 08 09 10 11 12 | if (field.isAnnotationPresent(Spy. class )) { field.setAccessible( true ); Object instance = field.get(testInstance); // 1 if (instance != null ) { // 2 Object spy = Mockito.spy(instance); field.set(testInstance, spy); } else { // 3 Class type = field.getType(); Object spy = Mockito.spy(type); field.set(testInstance, spy); } } |
Notice, that spies are used on real objects: either the test provides one at declaration time, or Mockito tries to create one. That’s where the if/then/else comes in.
- First we have to check whether the test created already in instance or not.
- If we had initialised the spy with a real object (because e.g. we have a complex constructor or whatever other reason), Mockito would use this existing instance.12
@Spy
Toaster toaster =
new
Toaster();
- However, our test only declares a field, but does not initialise it:12
@Spy
Toaster toaster;
In reality, Mockito would try to create a new instance based on the type, through the default constructor, if any.
Altogether, our simplified logic now roughly looks like:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | public void process(Class clazz, Object testInstance) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Mock. class )) { field.setAccessible( true ); Class type = field.getType(); Object mock = Mockito.mock(type); field.set(testInstance, mock); } if (field.isAnnotationPresent(Captor. class )) { field.setAccessible( true ); Class type = field.getType(); Object mock = ArgumentCaptor.forClass(type); field.set(testInstance, mock); } if (field.isAnnotationPresent(Spy. class )) { field.setAccessible( true ); Object instance = field.get(testInstance); if (instance != null ) { Object spy = Mockito.spy(instance); field.set(testInstance, spy); } else { Class type = field.getType(); Object spy = Mockito.spy(type); field.set(testInstance, spy); } } } } |
When you would use a debugger to look at the fields, you’d see that both toaster and coffee machine fields have been assigned some internal mock objects, created by Mockito.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | @ExtendWith (MockitoExtension. class ) public class WaitressTest { @Mock CoffeeMachine coffeeMachine; // CoffeeMachine$MockitoMock$170450874 @Spy Toaster toaster; // Toaster$MockitoMock$2027944578 @InjectMocks Waitress waitress; // still null @Test void should_do_something() { // .. } } |
Notice the weird-looking class names with the $-symbols in the names, that’s the kind of objects created by the Mockito.mock
and Mockito.spy
methods.
Inject mocks
After this phase, the mocks can be injected into Waitress
— which is still null
.
There and back again
We need to find all fields with the @InjectMocks
annotation, by basically iterating again all fields of the test class — and remember the fields for later.
1 2 3 4 5 6 7 | // scan all @InjectMocks fields Set injectMocksFields = new HashSet(); for (Field field : fields) { if (field.isAnnotationPresent(InjectMocks. class )) { injectMocksFields.add(field); } } |
Find all mocks and spies back again:
01 02 03 04 05 06 07 08 09 10 | // scan all mocks and spies again Set mocks = new HashSet(); for (Field field : fields) { field.setAccessible( true ); Object instance = field.get(testInstance); if (MockUtil.isMock(instance) || MockUtil.isSpy(instance)) { mocks.add(field); } } |
You might think, why are we again iterating all fields to check whether we have an instantiated mock or spy, when we recently just initialised them ourselves? Couldn’t we have remembered them in a set then, for later usage?
Well, in this simplistic example above: probably yes 😉
There are a few reasons why in reality Mockito separates these activities of (1) initialising + (2) finding them back for injection.
- More of secondary nature but still: Mockito has to take the entire hierarchy of the test class into account. Any parents of the test class can also define mocks which can be used for injection somewhere down the chain, for example. Keeping the state of both activities separated seems fairly practical.
- Both activities are actually independent. Even though the test might be littered with
@Mock
/@Spy
-initialised fields, it might never actually use@InjectMocks
. So why track the mocks, next to the fields themselves, additionally in some collection/list/set somewhere? Finding them back (if the need arises) seems to work out just fine.
Injection strategies
So, what to do with our mocks and @InjectMocks
-fields, which now contains our Waitress
field.
There are a few strategies to try: from an @InjectMocks
field…
- first we try to create an instance and pass all required mocks through a constructor
- if that doesn’t work, then try to create an instance and use property- and setter-injection
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | Set injectMocksFields = new HashSet(); // [Field] Set mocks = new HashSet(); // [CoffeeMachine$MockitoMock$170450874, // Toaster$MockitoMock$2027944578] //.. MockInjectionStrategy injectionStrategies = MockInjectionStrategy.nop() .thenTry( new ConstructorInjection()) .thenTry( new PropertyAndSetterInjection()); for (Field field : injectMocksFields) { injectionStrategies.process(field, testInstance, mocks); } |
In general, each strategy object tries to process the injection on its own accord and returns true
if it has worked, or false
if it failed, giving the next queued strategy a chance.
Constructor injection
If our Waitress
class would have a constructor e.g.
1 2 3 4 5 6 7 8 9 | class Waitress { private final CoffeeMachine coffeeMachine; private final Toaster toaster; Waitress(CoffeeMachine coffeeMachine, Toaster toaster) { this .coffeeMachine = coffeeMachine; this .toaster = toaster; } |
then ConstructorInjection
-strategy would resolve all parameters in the constructor and see which mocks are assignable to these types. Can Toaster$MockitoMock$2027944578
be assigned to type CoffeeMachine
? No. Can it be assigned to type Toaster
? Yes!
Next, can CoffeeMachine$MockitoMock$170450874
be assigned to type CoffeeMachine
? Yes!
There’s also a chance some “funny business” happens inside the constructor itself, which causes Mockito to fail constructing the instance under test 😉
So a new Waitress
instance is created, because both CoffeeMachine
and Toaster
mocks fit the two arguments of this constructor. There are a few cases where instantiating an @InjectMocks
field like this can fail, such as with abstract classes and interfaces.
Property and setter injection
If the Waitress
class would not have a constructor but just a few fields e.g.
1 2 3 4 5 6 | class Waitress { private CoffeeMachine coffeeMachine; private Toaster toaster; // Look ma, no constructor! |
the PropertyAndSetterInjection
-strategie would handle it perfectly!
This strategy would just try to instantiate through the default no-args constructor, effectively trying to do Waitress waitress = new Waitress()
.
Even if there is an explicit no-args constructor which has been made private it still works.
1 2 3 4 5 6 7 8 | class Waitress { private CoffeeMachine coffeeMachine; private Toaster toaster; private Waitress() { // private, but not for Mockito 🙂 } |
After Mockito has done new Waitress()
it both has to populate the private fields coffeeMachine
and toaster
inside that instance — they’re still uninitialised and null
.
Roughly it sorts the Waitress
fields a bit by name, filters out the final
and static
ones, iterates them and tries to assign a suitable mock from the mock candidates, either by setter or field access.
For instance, for every field Mockito first uses a setter (following the JavaBean standard) if present. If the following setCoffeeMachine
setter would be present…
1 2 3 4 5 6 7 8 9 | class Waitress { private CoffeeMachine coffeeMachine; private Toaster toaster; // bingo! public void setCoffeeMachine(CoffeeMachine coffeeMachine) { this .coffeeMachine = coffeeMachine; } |
…Mockito would invoke it with the mock:
1 2 | waitress.setCoffeeMachine(coffeeMachine /*CoffeeMachine$MockitoMock$170450874*/ ); |
However, if no setter-method can be found/invoked, Mockito tries to set the field directly (after making it accessible first, of course):
1 2 | waitress.coffeeMachine = coffeeMachine; // CoffeeMachine$MockitoMock$170450874 |
There are some risks with using @InjectMocks
like this: sometimes “it does not work” e.g. some fields are still uninitialised or null
after (you think) Mockito has done its work. Sometimes “weird” behaviour is wrongly attributed to Mockito: the test (read: developer) mixes up or forgets the proper Mockito initialisation techniques such as old-style-manually (initMocks()
), JUnit 4 @RunWith(MockitoJUnitRunner.class)
or JUnit 5 @ExtendWith(MockitoExtension.class)
or the developer uses TestNG which fails to do what JUnit does while expecting Mockito to do it 😉
A Hail Mock Mary, just as the very long forward pass in American football, is typically made in desperation, with only a small chance of success.
If the test infrastructure correctly leverages Mockito, there might still be issues with how the class under test has been designed (constructor which does not initialise all fields, again constructor which does not initialise all fields) or how the test has been designed (mixing same types, mixing different annotations, misuse, surprise, neglect or general Hail Mary’s)
Most of the times it’s not Mockito’s fault, it’s a question of reading the documentation and knowing what the framework does.
Ultimately when you’ve read the documentation and know what you’re doing, our @InjectMocks
-annotated field usually ends up as a properly initialised object. 🙂
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @ExtendWith (MockitoExtension. class ) public class WaitressTest { @Mock CoffeeMachine coffeeMachine; // CoffeeMachine$MockitoMock$170450874 @Spy Toaster toaster; // Toaster$MockitoMock$2027944578 @InjectMocks Waitress waitress; // Waitress{coffeeMachine=CoffeeMachine$MockitoMock$170450874, // toaster=Toaster$MockitoMock$2027944578} @Test void should_do_something() { // .. } } |
That’s how mocks are set up and injected. From here on, JUnit takes over again.
Conclusion
The code behind the Mock
/Spy
/…/InjectMocks
annotations take a great deal of boilerplate out of your tests, but come with the same advice as with any powertool: read the safety instructions first.
The modularity of the annotation engine, the use of the Reflection API, the injection strategies: how Mockito works internally can be an inspiration for any developer. Although some design choices have been made long ago I hope a small peek under the hood in this article will earn the Mockito contributors some admiration for their efforts and ingenuity. Use every annotation judiciously and appreciate those who make your life easier.
Published on Java Code Geeks with permission by Ted Vinke, partner at our JCG program. See the original article here: Mockito: Why You Still Should Appreciate InjectMocks Annotation Opinions expressed by Java Code Geeks contributors are their own. |