InjectMocks is wrongly injecting the same Mock into 2 different fields of similar type despite creating 2 different mocks

huangapple go评论76阅读模式
英文:

InjectMocks is wrongly injecting the same Mock into 2 different fields of similar type despite creating 2 different mocks

问题

我有一个类,其中有两个类似类型的字段。我已经模拟了它们两个。但是当我使用InjectMocks时,错误地将单个模拟注入到这两个字段中。

以下是示例代码类:

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

import java.util.Set;
import java.util.function.Consumer;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class TestClass {

    private final Consumer<Set<Integer>> intConsumer;

    private final Consumer<Set<String>> stringConsumer;

    void PrintClass(){
        System.out.println("intConsumers: " + intConsumer);
        System.out.println("stringConsumers: " + stringConsumer);
    }
}

以下是测试类:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Set;
import java.util.function.Consumer;

@RunWith(MockitoJUnitRunner.class)
public class TestClassTest {

    @Mock private Consumer<Set<Integer>> intConsumer;
    @Mock private Consumer<Set<String>> stringConsumer;
    @InjectMocks private TestClass testClass;

    @Test
    public void testPrint(){
        testClass.PrintClass();
    }
}

当我运行测试时的输出如下:testPrint() - intConsumer 被注入到 intConsumer 和 stringConsumer 中。

intConsumers: intConsumer
stringConsumers: intConsumer

Process finished with exit code 0

我正在使用 Maven。

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.7.19</version>
</dependency>

我专门为测试创建了这个私有构造函数,使用 InjectMocks 进行测试。我不想将其设为 public 或 package-private,因此我不能使用字段注入。我也不想使用公共 setter 方法来暴露这些字段。另外,我也不想将字段设为非 final。

我尝试过升级 mockito 版本到 3.5.10,但仍然存在这个问题。
我还尝试过将字段设为 final 并使用 setter 方法 - 这样注入就能正常工作 - 但我不想暴露我的 setter 方法。
我还尝试过使用构造函数注入并为模拟命名 @Mock(name = "mock"),但也不起作用。

我是否遗漏了什么?是否有办法使用私有构造函数注入使其正常工作?

英文:

I have a class which has 2 fields of similar types. I have mocked them both. But when I use InjectMocks, inject mocks wrongly injects a single mock into both those fields.

Here is the example code class:


import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

import java.util.Set;
import java.util.function.Consumer;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class TestClass {

    private final Consumer&lt;Set&lt;Integer&gt;&gt; intConsumer;

    private final Consumer&lt;Set&lt;String&gt;&gt; stringConsumer;

    void PrintClass(){
        System.out.println(&quot;intConsumers: &quot; + intConsumer);
        System.out.println(&quot;stringConsumers: &quot; + stringConsumer);
    }
}

Here is the test class:


import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Set;
import java.util.function.Consumer;

@RunWith(MockitoJUnitRunner.class)
public class TestClassTest {

    @Mock private Consumer&lt;Set&lt;Integer&gt;&gt; intConsumer;
    @Mock private Consumer&lt;Set&lt;String&gt;&gt; stringConsumer;
    @InjectMocks private TestClass testClass;

    @Test
    public void testPrint(){
        testClass.PrintClass();
    }


}

Here is the output when I run the test: testPrint() - intConsumer is injected into both intConsumer and stringConsumer.

intConsumers: intConsumer
stringConsumers: intConsumer



Process finished with exit code 0

I am using Maven.

&lt;dependency&gt;
        &lt;groupId&gt;org.mockito&lt;/groupId&gt;
        &lt;artifactId&gt;mockito-core&lt;/artifactId&gt;
        &lt;version&gt;2.7.19&lt;/version&gt;
&lt;/dependency&gt;

I created this private constructor especially for testing using InjectMocks. I do not want to make it public/package-private so I cannot use field injection. I also do not want to expose these fields using public setters. Also, I do not want to make my fields non-final.

I have tried upgrading the mockito-version to 3.5.10 but it still has this bug.
I have also tried making my fields final and using setters - then Injection works fine - but I do not want to expose my setters.
I have also tried naming mocks @Mock(name = "mock") with constructor injection but it does not work as well.

Am I missing something here? Is there a way to get it working with private constructor injection?

答案1

得分: 2

这是Mockito中的一个已知bug。

从我的观察中,PropertyAndSetterInjection考虑了泛型类型和@Mock的name属性,因此对于字段注入起作用如预期。但对于构造函数则不然,因为ConstructorInjection仅使用了SimpleArgumentResolver,这个解析器相当简单,并且没有像属性注入器那样的任何MockCandidateFilter。

通常,你可以:

  • 去掉@InjectMocks,在测试中的设置方法中构造TestClass实例。在我看来,这是一种较少侵入的方法。
  • 或者,如上所述,字段和setter注入是可行的。

但是在你的约束条件下(私有构造函数,无setter,final字段),这两种方法都不适用。

在这种情况下,你可以使用反射来构建实例:

@Before
public void setUp() throws IllegalAccessException, 
        InvocationTargetException, 
        InstantiationException,
        NoSuchMethodException {
    final Constructor<TestClass> constructor = TestClass.class.getDeclaredConstructor(Consumer.class, Consumer.class);
    constructor.setAccessible(true);
    testClass = constructor.newInstance(intConsumer, stringConsumer);
}
英文:

This is an open bug in Mockito.

> From what I see, PropertyAndSetterInjection takes generic types and @Mock's name attributes into account, so it works as expected for the field injection. But it doesn't work with constructors because ConstructorInjection is using only SimpleArgumentResolver which is well... very simple and doesn't have any MockCandidateFilter like property injector does.

Typically, you would:

  • drop @InjectMocks and construct TestClass instance in a setup method in test. IMHO this is less invasive approach.
  • alternatively, as mentioned above, field and setter injection work.

Neither of this approaches work with your constraints (private constructor, no setters, final fields).

In such case, you can resort to reflection to build the instance:

@Before
public void setUp() throws IllegalAccessException, 
        InvocationTargetException, 
        InstantiationException,
        NoSuchMethodException {
    final Constructor&lt;TestClass&gt; constructor = TestClass.class.getDeclaredConstructor(Consumer.class, Consumer.class);
    constructor.setAccessible(true);
    testClass = constructor.newInstance(intConsumer, stringConsumer);
}

huangapple
  • 本文由 发表于 2020年9月15日 02:11:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/63889701.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定