Oleg Tsal-Tsalko
Oleg Tsal-Tsalko

Reputation: 225

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?

Upvotes: 20

Views: 19850

Answers (2)

alltej
alltej

Reputation: 7285

This is documented in mockito as work around, if multiple mocks exists of the same type. It does not resolve the implementation based on the name provided (ie @Mock(name = "b2")). The algorithm it uses to resolved the implementation is by field name of the injected dependency. So your code above will resolve correctly (b2 => @Mock private B b2 and b3 => @Mock private B b3).

The other workaround is to use constructor injection which is the recommended way of injecting dependencies.

Upvotes: 0

Tim van der Lippe
Tim van der Lippe

Reputation: 768

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.

Upvotes: 26

Related Questions