初始化模拟对象 - 模拟对象

2022-08-31 07:22:20

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

1.

 public class SampleBaseTestCase {

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

建议我,如果还有其他方法比这些更好...


答案 1

对于模拟初始化,使用运行器或严格等效的解决方案。来自MockitoJUnitRunner的javadoc:MockitoAnnotations.initMocks

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


第一个解决方案(带有 ) 可以在测试用例上配置特定运行器(例如)时使用。MockitoAnnotations.initMocksSpringJUnit4ClassRunner

第二种解决方案(带有)是更经典和我最喜欢的。代码更简单。使用运行器提供了自动验证框架使用情况的巨大优势(由@David Wallace在此答案中描述)。MockitoJUnitRunner

两种解决方案都允许在测试方法之间共享模拟(和间谍)。再加上@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

缺点:测试不是自成一体的,代码的痛苦更少


答案 2

现在(从 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 子类,并使用它们来包装运行程序提供的测试语句。这样做的结果是,您可以提取@Before方法,@After方法,甚至尝试...将包装器捕获到规则中。您甚至可以在测试中与这些内容进行交互,就像EdusedException所做的那样。

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

综上所述:

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

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


推荐