Mockito @InjectMocks不适用于具有相同类型的字段

2022-09-02 10:12:54

我非常惊讶地发现,以下简单的代码示例不适用于>1.8.5的所有Mockito版本。

@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带注释的字段,否则Mockito可能会感到困惑,并且不会进行注入。

这是否意味着,如果我有多个具有相同类型的字段,我不能只模拟其中一个,而应该为具有相同类型的所有字段定义?它是否具有已知的限制,是否有任何原因尚未修复?按字段名称进行匹配应该很简单,不是吗?@Mock@Mock


答案 1

似乎Mockito使用其JavaDoc中描述的算法。

如果我理解正确,它将首先按类型排序(在本例中只有1 B),然后按名称排序(此处没有更改)。它最终将使用 OngoingInjector 接口实现进行注入,该实现似乎搜索第一个字段并注入它。

由于您只定义了 1 个 B,并且 Mock 中有 2 个 B 字段,因此它将看到第一个实例与该字段的匹配并停止。这是因为在NameBasedCandidateFilter中。因此,它将停止过滤并直接注入。如果创建多个相同类型的模拟,它们将按 Name 排序并相应地注入。mocks.size() == 1

当我创建特定类型的多个模拟(但少于字段数)时,我能够让它工作。

@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(在 A 中定义的前 2 个字段)。

您始终可以在他们的存储库中留下 GitHub 问题,并对注入筛选算法进行增强或更改,以便查看。


答案 2

这在 mockito 中记录为解决方法,如果存在相同类型的多个模拟。它不会根据提供的名称(即)解析实现。它用于解析实现的算法是通过注入依赖项的字段名称。因此,上面的代码将正确解析( => 和 => )。@Mock(name = "b2")b2@Mock private B b2b3@Mock private B b3

另一种解决方法是使用构造函数注入,这是注入依赖项的推荐方法。


推荐