Home » Java » Enterprise Java » Mockito: Why You Still Should Appreciate InjectMocks Annotation

About Ted Vinke

Ted Vinke
Ted is a Java software engineer with a passion for Web development and JVM languages and works for First8, a Java Web development company in the Netherlands.

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:

  1. All @Mock and @Spy-annotated fields (e.g. CoffeeMachine and Toaster) are initialized as mocks and spies
  2. 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?

  1. 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.
  2. The @Mock-annotated field (e.g. CoffeeMachine coffeeMachine in this case) could be private and yield an exception when we try to update it in step 4, so we have to (try to) make it accessible first.
  3. 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 generic Object.
  4. 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.

  1. First we have to check whether the test created already in instance or not.
  2. 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.

     

    1
    2
    @Spy
    Toaster toaster = new Toaster();
  3. However, our test only declares a field, but does not initialise it:
    1
    2
    @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…

  1. first we try to create an instance and pass all required mocks through a constructor
  2. 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.

(0 rating, 0 votes)
You need to be a registered member to rate this.
Start the discussion Views Tweet it!
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments