ChatGPT解决这个技术问题 Extra ChatGPT

初始化模拟对象 - MockIto

有很多方法可以使用 MockIto 初始化模拟对象。其中最好的方法是什么?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

@RunWith(MockitoJUnitRunner.class)

mock(XXX.class);

建议我是否有比这些更好的方法...


S
Skillz

对于 mocks 初始化,使用 runner 或 MockitoAnnotations.initMocks 是严格等效的解决方案。从 MockitoJUnitRunner 的 javadoc :

JUnit 4.5 runner 初始化使用 Mock 注释的模拟,因此不需要显式使用 MockitoAnnotations.initMocks(Object)。在每个测试方法之前初始化模拟。

当您已经在测试用例上配置了特定的运行器(例如 SpringJUnit4ClassRunner)时,可以使用第一个解决方案(使用 MockitoAnnotations.initMocks)。

第二个解决方案(使用 MockitoJUnitRunner)是更经典的,也是我最喜欢的。代码更简单。使用跑步者提供了 automatic validation of framework usage 的巨大优势(由 this answer 中的 @David Wallace 描述)。

两种解决方案都允许在测试方法之间共享模拟(和间谍)。再加上 @InjectMocks,它们允许非常快速地编写单元测试。样板模拟代码减少了,测试更容易阅读。例如:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

优点:代码很少

缺点:黑魔法。 IMO 这主要是由于 @InjectMocks 注释。使用此注释“您可以摆脱代码的痛苦”(请参阅 @Brice 的精彩评论)

第三种解决方案是在每个测试方法上创建你的模拟。正如 @mlk 在其答案中所解释的那样,它允许具有“独立测试”。

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

优点:你清楚地展示了你的 api 是如何工作的(BDD ......)

缺点:有更多样板代码。 (模拟创作)

我的建议是妥协。将 @Mock 注释与 @RunWith(MockitoJUnitRunner.class) 一起使用,但不要使用 @InjectMocks

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

优点:您清楚地展示了您的 api 是如何工作的(我的 ArticleManager 是如何实例化的)。没有样板代码。

缺点:测试不是独立的,减少代码的痛苦


不过要小心,注释很有用,但它们并不能保护您免受糟糕的 OO 设计(或降级)的影响。就个人而言,虽然我很高兴减少样板代码,但我摆脱了代码(或 PITA)的痛苦,这是将设计更改为更好的触发器,因此我和团队正在关注 OO 设计。我觉得遵循像 SOLID 设计或 GOOS 思想这样的原则的 OO 设计比选择如何实例化模拟更重要。
(跟进)如果你没有看到这个对象是如何创建的,你不会感到痛苦,如果应该添加新功能,未来的程序员可能反应不佳。无论如何,这两种方式都有争议,我只是说要小心。
这两者等价是不正确的。更简单的代码是使用 MockitoJUnitRunner 的唯一优势是不正确的。有关差异的更多信息,请参阅 stackoverflow.com/questions/10806345/… 中的问题和我对此的回答。
@Gontard 是的,依赖项是可见的,但我看到使用这种方法的代码出错了。关于使用Collaborator collab = mock(Collaborator.class),我认为这种方式肯定是一种有效的方法。虽然这可能会很冗长,但您可以获得测试的可理解性和可重构性。两种方式各有利弊,我还没有决定哪种方法更好。 Amyway 总是有可能写废话,并且可能取决于上下文和编码器。
@mlk 我完全同意你的看法。我的英语不是很好,而且缺乏细微差别。我的意思是坚持使用 UNIT 这个词。
C
Community

现在(从 v1.10.7 开始)有第四种方法来实例化模拟,它使用称为 MockitoRule 的 JUnit4 规则

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit 查找 subclasses of TestRule annotated with @Rule,并使用它们包装 Runner 提供的测试语句。这样做的结果是,您可以将 @Before 方法、@After 方法,甚至 try...catch 包装器提取到规则中。您甚至可以在测试中与这些交互,就像 ExpectedException 所做的那样。

MockitoRule 的行为几乎与 MockitoJUnitRunner 完全一样,除了您可以使用任何其他运行程序,例如 Parameterized(它允许您的测试构造函数接受参数,以便您的测试可以运行多次),或者 Robolectric 的测试runner(因此它的类加载器可以为 Android 原生类提供 Java 替换)。这使得它在最近的 JUnit 和 Mockito 版本中使用起来更加灵活。

总之:

Mockito.mock():直接调用,不支持注释或使用验证。

MockitoAnnotations.initMocks(this):注解支持,无使用验证。

MockitoJUnitRunner:注解支持和使用验证,但你必须使用那个运行器。

MockitoRule:任何 JUnit 运行器的注释支持和使用验证。

另请参阅:How JUnit @Rule works?


在 Kotlin 中,规则如下所示:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
f
fl0w

JUnit 5 Jupiter 的一个小例子,“RunWith”已被删除,您现在需要使用“@ExtendWith”注释来使用扩展。

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

T
Thanthu

1. 使用 MockitoAnnotations.openMocks():

Mockito 2 中的 MockitoAnnotations.initMock() 方法已弃用,并在 Mockito 3 中替换为 MockitoAnnotations.openMocks()MockitoAnnotations.openMocks() 方法返回一个 AutoClosable 实例,可用于在测试后关闭资源。下面是使用 MockitoAnnotations.openMocks() 的示例。

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;


class MyTestClass {

    AutoCloseable openMocks;

    @BeforeEach
    void setUp() {
        openMocks = MockitoAnnotations.openMocks(this);
        // my setup code...
    }

    @Test
    void myTest() {
        // my test code...
        
    }

    @AfterEach
    void tearDown() throws Exception {
        // my tear down code...
        openMocks.close();
    }

}

2. 使用@ExtendWith(MockitoExtension.class):

从 JUnit5 开始,@RunWith 已被删除。以下是使用 @ExtendWith 的示例:

@ExtendWith(MockitoExtension.class)
class MyTestClass {

    @BeforeEach
    void setUp() {
        // my setup code...
    }

    @Test
    void myTest() {
        // my test code...

    }

    @AfterEach
    void tearDown() throws Exception {
        // my tear down code...
    }

}

我正在将使用 JUnit4 和 Mockito 2 的代码转换为 Jupiter 和 Mockito 3。感谢您提供最新的答案!将 MockitoAnnotations.initMocks(this); 更新到示例 1 效果很好。大多数其他答案现在已经过时了。
实际上它已在 Mockito 3.4.0 中被弃用,而不是在 Mockito 3 中。您不会在 Mockito 3.3.0 中找到 openMocks 方法
M
Michael Lloyd Lee mlk

MockitoAnnotations 和 runner 已经在上面进行了很好的讨论,所以我要为不受欢迎的人投入我的 tuppence:

XXX mockedXxx = mock(XXX.class);

我使用它是因为我发现它更具描述性,并且我更喜欢(不是完全禁止)单元测试不使用成员变量,因为我喜欢我的测试(尽可能地)自包含。


除了使测试用例自包含之外,使用 mock(XX.class) 是否还有其他优势?
为了阅读测试而必须理解的魔法更少。你声明变量,并给它一个值——没有注释、反射等。
e
emd

有一种巧妙的方法可以做到这一点。

如果是单元测试,你可以这样做:@RunWith(MockitoJUnitRunner.class) public class MyUnitTest { @Mock private MyFirstMock myFirstMock; @Mock private MySecondMock mySecondMock; @Spy private MySpiedClass mySpiedClass = new MySpiedClass(); // 它将向该对象注入 2 个模拟和每个反射的间谍对象 // @InjectMocks 的 java 文档很好地解释了它如何以及何时进行注入 @InjectMocks private MyClassToTest myClassToTest; @Test public void testSomething() { } }

编辑:如果它是一个集成测试,你可以这样做(不打算与 Spring 一起使用。只是展示你可以用不同的 Runners 初始化模拟):@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("aplicationContext.xml")公共类 MyIntegrationTest { @Mock private MyFirstMock myFirstMock; @Mock private MySecondMock mySecondMock; @Spy private MySpiedClass mySpiedClass = new MySpiedClass(); // 它将向该对象注入 2 个模拟和每个反射的间谍对象 // @InjectMocks 的 java 文档很好地解释了它如何以及何时进行注入 @InjectMocks private MyClassToTest myClassToTest; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void testSomething() { } }


如果 MOCK 也参与集成测试,这有意义吗?
实际上它不会,你的权利。我只是想展示 Mockito 的可能性。例如,如果您使用 RESTFuse,您必须使用他们的跑步者,以便您可以使用 MockitoAnnotations.initMocks(this); 初始化模拟。
S
Slawomir Jaranowski

Mockito 的最新版本中,方法 MockitoAnnotations.initMocks 已弃用

首选方式是使用

JUnit4 的 MockitoJUnitRunner 或 MockitoRule

JUnit5 的 MockitoExtension

TestNG 的 MockitoTestNGListener

如果您不能使用专用的跑步者/扩展程序,您可以使用 MockitoSession


n
neXus

如果您需要/需要,其他答案很棒,并且包含更多详细信息。除此之外,我想添加一个 TL;DR:

更喜欢使用@RunWith(MockitoJUnitRunner.class) 如果你不能(因为你已经使用了不同的跑步者),更喜欢使用@Rule public MockitoRule rule = MockitoJUnit.rule();类似于(2),但你不应该再使用它了:@Before public void initMocks() { MockitoAnnotations.initMocks(this);如果您只想在其中一个测试中使用模拟并且不想将其暴露给同一测试类中的其他测试,请使用 X x = mock(X.class)

(1) 和 (2) 和 (3) 是互斥的。 (4) 可与其他组合使用。