Mockito 在为 InjectMocks 带注释的对象调用 openMocks 后,用新对象替换了已经模拟的对象

Mockito replaced already mocked objects with new ones after calling openMocks for InjectMocks annotated object

提问人:user1589188 提问时间:11/15/2023 更新时间:11/15/2023 访问量:23

问:

我有一个奇怪的情况,即 Mockito 在调用带注释的对象后用新对象替换已经模拟的对象。openMocks()@InjectMocks

这是我的设置:

@RunWith(MockitoJUnitRunner.class) public class MyTest {
    @Mock ClassA mockClassA;
    @Mock ClassB mockClassB;
    @InjectMocks ClassToTest classToTest;
    AutoCloseable closeable;
    @Before public void openMocks() {
        closeable = MockitoAnnotations.openMocks(this);
    }
    //...
}

public class ClassToTest {
    @Inject private ClassA classA;
    private final ClassB classB;
    @Inject public ClassToTest(ClassB classB) {
        this.classB = classB;
    }
}

P.S. 我无法控制,我无法重写它以将私有成员移动到构造函数。ClassToTestclassA

在这里,我想同时嘲笑 和 ,而测试是针对 .由于不是通过构造函数设置的,我需要使用 Mockito 为我设置它。它确实在测试中正确设置了它。classAclassBClassToTestclassA@InjectMocksmockClassA

但是,问题出在 .我在之前设置了一个断点,在那里我可以看到 = 和 = ,这是预期的。然后调用后,我可以看到 = ,这很好。但现在变成了一个新的模拟对象,不再是了。由于我需要设置 模拟返回 ,因此用新的模拟替换它是行不通的。classBopenMocks()classToTest.classAnullclassToTest.classBmockClassBopenMocks()classToTest.classAmockClassAclassToTest.classBmockClassBmockClassB

有没有办法告诉 Mockito 不要换成新的模拟?classB

java 单元测试 依赖注入 mockito guice

评论


答:

1赞 knittl 11/15/2023 #1

@RunWith(MockitoJUnitRunner.class)应该已经初始化了所有 -and -annotated 字段。@Mock@InjectMocks

MockitoAnnotations.openMocks仅当您不使用注释并且必须手动初始化字段时才需要。

@RunWith(MockitoJUnitRunner.class)
public class MyTest {
    @Mock ClassA mockClassA;
    @Mock ClassB mockClassB;
    @InjectMocks ClassToTest classToTest;
    @Before public void setup() {
        when(mockClassB.someCall()).thenReturn(whatever);
    }

    @Test public void test() {
        // use this.classToTest
    }
}

通过直接分配字段,可以在没有注释的情况下做到这一点:@Mock

@RunWith(MockitoJUnitRunner.class)
public class MyTest {
    ClassA mockClassA = mock(ClassA.class);
    ClassB mockClassB = mock(ClassB.class);
    @InjectMocks ClassToTest classToTest;
    @Before public void setup() {
        when(mockClassB.someCall()).thenReturn(whatever);
    }

    @Test public void test() {
        // use this.classToTest
    }
}

public class MyTest {
    ClassA mockClassA = mock(ClassA.class);
    ClassB mockClassB = mock(ClassB.class);
    @InjectMocks ClassToTest classToTest;
    AutoCloseable closeable;
    @Before public void setup() {
        closeable = MockitoAnnotations.openMocks(this);
        when(mockClassB.someCall()).thenReturn(whatever);
    }

    @Test public void test() {
        // use this.classToTest
    }
}

或者,手动创建所有模拟对象,并使用反射来分配被测类的字段。

当然,将类设计为可测试的类总是值得的,这意味着它们的依赖项是通过构造函数传递的。

这类似于为什么在执行单元测试时未调用我的模拟方法?(对两个不同模拟对象的不同引用,测试引用一个对象,类引用另一个模拟对象)。

评论

0赞 user1589188 11/15/2023
谢谢 knittl,我可以确认你所说的关于设置模拟的内容已经是真的。但是,它没有为我分配(即使已创建),这就是我需要额外.似乎问题可能是“如何告诉 Mockito 也通过分配?MockitoJUnitRunnerclassAclassToTestopenMocks()classAMockitoJUnitRunner
0赞 knittl 11/15/2023
@user1589188 什么是()?它应该由 Mockito 创建和分配。它是一个类还是一个接口?你通常不应该模拟课程。它有公共构造函数吗?(创建它很奇怪,但注释没有——我希望注释在幕后执行相同的方法调用)classAmockClassAopenMocks
0赞 knittl 11/15/2023
@user1589188找到更新的答案,也许这很有帮助。没有看到 和 ,就很难给出答案。迁移到 JUnit 5 可能也是值得的,因为 JUnit 4 已经过时了。ClassAClassB
0赞 user1589188 11/15/2023
谢谢!不使用注释就起作用了!mock()
0赞 user1589188 11/15/2023 #2

多亏了 @knittl 的想法,我最终得到了这个完善的解决方案:

//@RunWith(MockitoJUnitRunner.class) no need
public class MyTest {
    @Mock ClassA mockClassA;
    ClassB mockClassB = mock(ClassB.class);
    @InjectMocks ClassToTest classToTest = new ClassToTest(mockClassB);
    AutoCloseable closeable;
    @Before public void openMocks() {
        closeable = MockitoAnnotations.openMocks(this);
    }
    //...
}