153

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

1.

 public class SampleBaseTestCase {

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

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

4

8 回答 8

176

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

JUnit 4.5 runner 初始化使用 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 ......)

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


的建议是妥协。将@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实例化我的)。没有样板代码。

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

于 2013-03-19T08:53:40.753 回答
33

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

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

总之:

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

另请参阅:JUnit @Rule 如何工作?

于 2015-07-22T23:56:37.810 回答
10

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

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

    @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() {
        }
    }
    
  • 编辑:如果它是一个集成测试,你可以这样做(不打算与 Spring 一起使用。只是展示你可以用不同的跑步者初始化模拟):

    @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() {
        }
    }
    
于 2013-03-19T09:48:12.540 回答
10

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

XXX mockedXxx = mock(XXX.class);

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

于 2013-03-19T10:06:56.690 回答
8

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

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}
于 2020-04-01T10:00:42.363 回答
4

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

首选方式是使用

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

于 2021-03-02T21:57:42.087 回答
4

1. 使用 MockitoAnnotations.openMocks()

Mockito 2 中的MockitoAnnotations.initMock()方法已弃用并替换为MockitoAnnotations.openMocks()Mockito 3 中的方法。该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...
    }

}
于 2021-08-08T14:03:34.557 回答
2

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

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

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

于 2018-11-23T12:44:36.223 回答