Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito @InjectMocks doesn't work for fields with same type

I was very surprised to find out that following simple code example doesn't work for all Mockito versions > 1.8.5

@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {

    @Mock(name = "b2")
    private B b2;

    @InjectMocks
    private A a;

    @Test
    public void testInjection() throws Exception {
        assertNotNull(a.b2); //fails
        assertNull(a.b1); //also fails, because unexpectedly b2 mock gets injected here
    }

    static class A{
        private B b1;
        private B b2;
    }

    interface B{}
}

In javadocs (http://docs.mockito.googlecode.com/hg/latest/org/mockito/InjectMocks.html) there is a quote:

Note 1: If you have fields with the same type (or same erasure), it's better to name all @Mock annotated fields with the matching fields, otherwise Mockito might get confused and injection won't happen.

Does it mean that if I have several fields with same type I can't mock ONLY ONE of them but rather should define @Mock for ALL fields with same type? Is it known limitation and is there any reason why it wasn't fixed yet? It should be straightforward to match @Mock by fields names, isn't it?

like image 602
Oleg Tsal-Tsalko Avatar asked Apr 29 '15 18:04

Oleg Tsal-Tsalko


People also ask

Can we use @SPY and @InjectMocks together?

@Spy and @InjectMocks cannot be used well together (see Google Code issue #489 and GitHub issue #169), and for what they do it is not clear or common that they should be used together at all. In well-written Mockito usage, you generally should not even want to apply them to the same object.

What is the difference between @mock and @InjectMocks in Mockito framework?

@InjectMocks creates an instance of the class and injects the mocks that are created with the @Mock annotations into this instance. @Mock is used to create mocks that are needed to support the testing of the class to be tested. @InjectMocks is used to create class instances that need to be tested in the test class.

What does @InjectMocks do Mockito?

@InjectMocks is the Mockito Annotation. It allows you to mark a field on which an injection is to be performed. Injection allows you to, Enable shorthand mock and spy injections.

What is the difference between Mockbean and InjectMocks?

@Mock is used to declare/mock the references of the dependent beans, while @InjectMocks is used to mock the bean for which test is being created.


1 Answers

It appears Mockito uses an algorithm described in their JavaDoc

If I understand correctly, it will first sort on type (in this case only 1 B) and then sort on name (no changes here). It will finally inject using the OngoingInjector interface implementation, which appears to search for the first field and inject it.

Since you only have 1 B defined and there are 2 fields of B in the Mock, it will see the match of the first instance to the field and stop. This is because mocks.size() == 1 in NameBasedCandidateFilter . Therefore it will stop filtering and inject it directly. If you create multiple mocks of the same type, they will get sorted on Name and injected accordingly.

I was able to get it work when I created multiple mocks (but less than the number of fields) of a specific type.

@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {

    @Mock(name = "b2")
    private B b2;

    @Mock(name = "b3")
    private B b3;

    @InjectMocks
    private A a;

    @Test
    public void testInjection() {
        System.out.println(this.a);
    }

    static class A {

        private B b1;

        private B b2;

        private B b3;
    }

    interface B {
    }
}

This will correctly inject b2 into a.b2 and b3 into a.b3 instead of a.b1 and a.b2 (the first 2 fields that are defined in A).

You can always leave a GitHub issue on their repository with an enhancement or change on the injection filtering algorithm in order to be looked at.

like image 129
Tim van der Lippe Avatar answered Oct 22 '22 09:10

Tim van der Lippe