初始化模拟对象-Mockito

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

1.

 public class SampleBaseTestCase {


@Before public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@RunWith(MockitoJUnitRunner.class)
mock(XXX.class);

告诉我有没有比这更好的方法。

310633 次浏览

对于 mock 初始化 ,使用 Runner 或 MockitoAnnotations.initMocks是严格等效的解决方案。来自 MockitoJUnitRunner的 javadoc:

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


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

第二个解决方案(与 MockitoJUnitRunner)是更经典和我的最爱。代码更简单。使用流道提供了 自动验证框架使用的巨大优势(由 这个答案中的 @ 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...)

缺点: 有更多的样板代码(模拟创建)


我的 建议是一个折衷方案。在 @RunWith(MockitoJUnitRunner.class)中使用 @Mock注释,但不要使用 @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是如何实例化的)。

缺点: 测试不是自包含的,代码的痛苦较少

做这件事的方法很简单。

  • 如果是单元测试,你可以这样做:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
    
    @Mock
    private MyFirstMock myFirstMock;
    
    
    @Mock
    private MySecondMock mySecondMock;
    
    
    @Spy
    private MySpiedClass mySpiedClass = new MySpiedClass();
    
    
    // It's gonna inject the 2 mocks and the spied object per reflection to this object
    // The java doc of @InjectMocks explains it really well how and when it does the injection
    @InjectMocks
    private MyClassToTest myClassToTest;
    
    
    @Test
    public void testSomething() {
    }
    }
    
  • EDIT: If it's an Integration test you can do this(not intended to be used that way with Spring. Just showcase that you can initialize mocks with diferent Runners):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
    
    @Mock
    private MyFirstMock myFirstMock;
    
    
    @Mock
    private MySecondMock mySecondMock;
    
    
    @Spy
    private MySpiedClass mySpiedClass = new MySpiedClass();
    
    
    // It's gonna inject the 2 mocks and the spied object per reflection to this object
    // The java doc of @InjectMocks explains it really well how and when it does the injection
    @InjectMocks
    private MyClassToTest myClassToTest;
    
    
    @Before
    public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    }
    
    
    @Test
    public void testSomething() {
    }
    }
    

MockitoAnnotations & the runner 已经在上面讨论过了,所以我要把我的两便士扔给那些不受欢迎的人:

XXX mockedXxx = mock(XXX.class);

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

现在(从1.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 查找 用@Rule 注释的 TestRule 的子类,并将它们用于 包装 Runner 提供的测试语句。这样做的结果是,您可以提取@Before 方法、@After 方法,甚至可以尝试... catch wrappers 到规则中。您甚至可以在测试中与它们交互,就像 例外所做的那样。

MockitoRule 表现为 几乎和 MockitoJUnitRunner 一模一样,除了你可以使用任何其他的运行器,比如 参数化(它允许你的测试构造器接受参数,这样你的测试可以运行多次) ,或者 Robolecric 的测试运行器(这样它的类加载器可以为 Android 本地类提供 Java 替代品)。这使得它在最近的 JUnit 和 Mockito 版本中的使用更加灵活。

总之:

  • Mockito.mock(): 没有注释支持或使用验证的直接调用。
  • MockitoAnnotations.initMocks(this): 支持注释,没有使用验证。
  • MockitoJUnitRunner: 注释支持和使用验证,但您必须使用该运行程序。
  • MockitoRule: 任何 JUnit 运行器的注释支持和使用验证。

参见: JUnit@Rule 是如何工作的?

其他的答案都很棒,如果你想/需要的话,还可以提供更多的细节。
除此之外,我还想添加一个 TL; DR:

  1. 我更喜欢用
    • @RunWith(MockitoJUnitRunner.class)
  2. 如果你不能(因为你已经使用了一个不同的跑步机) ,宁愿使用
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. 类似于(2) ,但是你应该使用 没有:
    • @ 在 public void initMocks (){ 模仿(this) ; }
  4. 如果希望在其中一个测试中使用模拟,并且不希望将其公开给同一测试类中的其他测试,请使用
    • X x = mock(X.class)

(1)和(2)和(3)是相互排斥的。
(4)可与其他两者结合使用。

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

@ExtendWith(MockitoExtension.class)
class FooTest {


@InjectMocks
ClassUnderTest test = new ClassUnderTest();


@Spy
SomeInject bla = new SomeInject();
}

Mockito的最新版本中,不推荐使用 MockitoAnnotations.initMocks方法

首选方法是使用

如果你不能使用专用的运行器/扩展,你可以使用 MockitoSession

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...
}


}