Mockito @InjectMocks не работает для полей того же типа

Я был очень удивлен, узнав, что следующий простой пример кода не работает для всех версий Mockito > 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{}
}

В javadocs (http://docs.mockito.googlecode.com/hg/latest/org/mockito/InjectMocks.html) есть цитата:

Примечание 1: Если у вас есть поля с тем же типом (или с тем же стиранием), это лучше назвать все аннотированные поля @Mock соответствующими полями, иначе Мокито может запутаться, и инъекции не произойдет.

Означает ли это, что если у меня есть несколько полей с одним и тем же типом, я не могу имитировать ТОЛЬКО ОДИН из них, а должен определять @Mock для полей ВСЕ с тем же типом? Известно ли это ограничение и есть ли причина, почему он еще не исправлен? Это должно быть просто, чтобы соответствовать @Mock по именам полей, не так ли?

Ответы

Ответ 1

Похоже, Mockito использует алгоритм, описанный в их JavaDoc

Если я правильно понял, он сначала сортирует по типу (в данном случае только 1 B), а затем сортирует по имени (здесь нет изменений). Он, наконец, будет использовать реализацию интерфейса OngoingInjector, который, как представляется, ищет первое поле и вводит его.

Поскольку у вас только 1 B определено и есть 2 поля B в Mock, он увидит совпадение первого экземпляра с полем и остановится. Это связано с тем, что mocks.size() == 1 в NameBasedCandidateFilter , Поэтому он прекращает фильтрацию и вводит ее напрямую. Если вы создадите несколько макетов одного типа, они будут отсортированы по имени и соответственно введены.

Мне удалось заставить его работать, когда я создал несколько mocks (но меньше числа полей) определенного типа.

@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 {
    }
}

Это правильно введет b2 в a.b2 и b3 в a.b3 вместо a.b1 и a.b2 (первые 2 поля, которые определены в A).

Вы всегда можете оставить проблему GitHub в своем репозитории с улучшением или изменением алгоритма фильтрации инъекций для просмотра.

Ответ 2

Это документировано в mockito как работа, если несколько макетов существуют одного типа. Он не разрешает реализацию на основе предоставленного имени (т.е. @Mock(name = "b2")). Алгоритм, который он использует для разрешения реализации, - это имя поля вложенной зависимости. Таким образом, ваш код выше правильно разрешит (b2 = > @Mock private B b2 и b3 = > @Mock private B b3).

Другим обходным путем является использование инъекции конструктора, которая является рекомендуемым способом инъекции зависимостей.