【问题标题】:mockito "verify" checks current state, but does not reset mock invocations模拟“验证”检查当前状态,但不重置模拟调用
【发布时间】:2015-12-21 12:16:36
【问题描述】:

我在 JUnit 测试中使用了 Mockito。测试是集成测试,测试整个场景,所以里面有很多断言和verify(mock)s。

我的问题是,我正在编写一些生产代码,做一些断言,然后验证模拟是否有效,直到有一个相同的模拟调用。查看简化代码:

interface I {
    void m1();
    void m2();
}

// some decorator
class T implements I {
    public T(I decorated) {
        /*...*/
    }

    /* ... */
}

public void testInvalid() {
    I m = mock(I.class);
    T t = new T(m);

    t.m1();
    assertEquals("m1", t.getLastMethod());
    verify(m).m1();

    t.m2();
    assertEquals("m2", t.getLastMethod());
    verify(m).m2();

    t.m1();
    assertEquals("m1", t.getLastMethod());
    verify(m).m1();

    // TooManyActualInvocations t.m1(): Wanted 1 time, but was 2 times ...
}

public void testValid() {
    I m = mock(I.class);
    T t = new T(m);

    t.m1();
    assertEquals("m1", t.getLastMethod());

    t.m2();
    assertEquals("m2", t.getLastMethod());

    t.m1();
    assertEquals("m1", t.getLastMethod());

    verify(m, times(2)).m1();
    verify(m).m2();
}

一个想法是在最后验证模拟,但假设有一个小的愚蠢的实现错误导致调用方法 m1 两次和一次 m2 但不是我在testInvalid 中所期望的那样,但最后测试会通过。我希望我的测试尽早失败。我该如何做到这一点?

谢谢。

感谢@Woozy Coder:

没有提到,reset 也是一个选项,但由于它必须在验证和下一个相等的存根调用之间调用,所以我认为很难编写“好的”和正确的测试。应该有两种不同的 mocking 风格:

  1. “后置条件”像 Mockito 那样模拟
  2. “早期”模拟,这将是验证块后的隐式重置

类似:

earlyReset(m).after(
    new Runnable() {
        t.someMethodInvokingTwoStubs();

        verify(m).someMethod1();
        verify(m).someMethod2();
    }
);

【问题讨论】:

  • 试试这个answer
  • 不要在一个测试方法中测试多个方法。一个测试应该有一个可以测试的东西,最好是一个可能失败的东西。测试多种方法会使您的测试更加复杂。将您的测试分成多个测试方法将解决这个问题。
  • @WoozyCoder 谢谢,编辑了我的帖子
  • @FlorianSchaetz 这是一个集成测试。它测试了一个有限状态机,分别是 TDD,因为我想编写一些生产代码来帮助我设计它。它使用的所有组件都经过单元测试,但状态机使用了非常多的反射,而我的测试是带有内部测试类和注释的场景。

标签: java junit mockito


【解决方案1】:

我遇到了类似的问题并决定使用clearInvocations()(在 Mockito 2.1 中使用)

https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/Mockito.html#clearInvocations-T...-

使用reset() 的缺点是您也会松开存根,clearInvocations() 只会清除调用。

【讨论】:

  • 非常感谢。这拯救了我的一天!
【解决方案2】:

Mockito 是为了避免脆弱性而编写的,这样验证可以使最不具体的断言成为可能,从而允许实现在不更改测试的情况下发展。 如果您多次调用这些方法对您的测试系统无关紧要,那么您不应该要求 Mockito 进行检查。

替代方案:

  • 使用atLeastatLeastOnce 确保调用完全发生,而无需担心该方法被调用了多少额外次。
  • 如果调用被存根以具有返回值,则推断系统根据关于您存根的数据的状态断言工作。
  • 如果您确实需要存根或验证行为来更改单个测试方法,您的模拟可能会超出 Mockito 擅长的范围。对单个方法使用答案,或编写手动 Fake 以正确模拟互连的方法。

【讨论】:

  • 我想过用我的状态机实现一个售票机。它从一个 IdleState 开始,应该调用 onIdle,然后我们选择一张票,支付它,它应该返回 IdleState 并再次调用 onIdle。这是一个安静的复杂测试,但它是状态机实现以及功能文档的基础。一开始我希望 onIdle 被调用,最后在支付票后。最后的atLeast(2) 对我来说太模糊了。我觉得 Mockito 不适合做这样的集成测试,但是我确实很喜欢 Mockito,因为它好用
猜你喜欢
  • 2018-08-26
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多