初始化模拟对象——Mockito



有许多方法可以使用mockit初始化模拟对象。哪一种方法是最好的?

1。

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

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

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

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


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

    第二种解决方案(使用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));
        }
    }
    
    优点:代码最少 缺点:黑魔法。在我看来,这主要是由于@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是如何实例化的)。没有样板代码。

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

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

    @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方法,甚至尝试…将包装器捕获到规则中。您甚至可以在测试中与它们交互,就像ExpectedException那样。

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

    在简介:

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

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

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

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

    @ExtendWith(MockitoExtension.class)
    class FooTest {
      @InjectMocks
      ClassUnderTest test = new ClassUnderTest();
      @Spy
      SomeInject bla = new SomeInject();
    }
    

    MockitoAnnotations &上面已经讨论过了,所以我要把我的两便士给那些不被爱的人:

    XXX mockedXxx = mock(XXX.class);
    

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

    有一种简洁的方法。

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

      @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中使用这种方式)。只是展示您可以使用不同的runner初始化mock):

      @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() {
          }
      }
      

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

    首选方法是使用

      JUnit4 MockitoJUnitRunnerMockitoRuleMockitoExtension for JUnit5 MockitoTestNGListener for TestNG

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

    其他答案是伟大的,包含更多的细节,如果你想/需要他们。
    除此之外,我还想添加一个TL;DR:

    1. 优先使用
      • @RunWith(MockitoJUnitRunner.class)
    2. 如果不能(因为已经使用了不同的运行器),建议使用
      • @Rule public MockitoRule rule = MockitoJUnit.rule();
    3. 类似于(2),但你应该不再使用这个:
      • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
    4. 如果您只希望在一个测试中使用mock,并且不希望将其暴露给同一测试类中的其他测试,则使用
      • X x = mock(X.class)

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

    最新更新