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

2

20

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?

Streptomycin answered 29/4, 2015 at 18:52 Comment(0)
D
24

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.

Distant answered 29/4, 2015 at 20:47 Comment(2)
Actually injection could certainly be made a bit smarter for such legitimate cases, but safeness is mandatory. Injection is happening kinda automatically, so Mockito should behave quite reluctantly to avoid doing bad things. Also if injection is complicated then your object is either too complicated or has to follow another pattern of creation (a Joshua Bloch builder for example)Maziar
This is a known issue and your above code is a work around when mocking interfaces of the same type exists in the system under test.Civvies
C
0

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.

Civvies answered 31/5, 2017 at 13:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.