【问题标题】:Can Mockito verify parameters based on their values at the time of method call?Mockito 可以在方法调用时根据参数值验证参数吗?
【发布时间】:2012-02-23 12:54:32
【问题描述】:

我有一个 Foo 类,它是 SUT 和一个 Bar 类,它是它的合作者。 FooBar 上调用 run(List<Object> values),并使用“expectedList”作为参数。然后,Foo 将向此List 添加更多元素,使其状态与调用run() 时的状态不同。这是我的测试用例。

@Test
public void testFoo() {
    Bar collaborator = spy(new Bar()); 
    Foo sut = new Foo(collaborator);
    verify(collaborator).run(expectedList);
}

请注意,协作者实际上是一个间谍对象,而不是一个模拟对象。这个测试用例将失败,因为即使 run() 是使用等于 expectedList 的参数调用的,但它已被修改,并且其当前值不再等于 expectedList。然而,这是它应该工作的方式,所以我想知道是否有办法让 Mockito 在调用方法时存储参数的快照,并根据这些值而不是最近的值来验证它们。

【问题讨论】:

    标签: java unit-testing mockito


    【解决方案1】:

    在调用方法时使用Answer 检查参数的值。如果值错误,您可以在Answer 中抛出AssertionError,或者您可以存储该值,并在最后进行断言。

    【讨论】:

    • 是的,大卫是对的。由于 Mockito 的 API 的制作方式,无法使用相同的参数引用验证多个调用。 EasyMock 可以做到这一点,因为它在生产代码运行之前有一个预期阶段。无论如何,而不是 Answer 我使用 ArgurmentCaptor 并在该列表的最终状态上写一个或多个断言,即使用 FEST-Assert assertThat(captor.getValue()).contains("A", "B").contains("T", "U");
    • @Brice - 这与 Michael Wiles 的方法有何不同?
    • 不是。这只是实现测试目的的不同方式。因为大多数时候你并不真正需要检查中间参数,而只是检查发生了一些交互和最终结果。虽然我必须说如果汤姆有特定的要求,那么同意这对他没有帮助,但在这种情况下,我会避免在我的生产代码中使用可变对象。感觉就像两个协作者之间的消息传递应该始终是不可变的。
    • @Brice,在验证传递给模拟的参数时,匹配实例的最终状态在逻辑上似乎不正确,因为那不是模拟交互时的状态。
    • @NieldeWet 是的,这就是两个合作者之间 消息 的不变性的全部意义 ;)
    【解决方案2】:

    The answer of Dawood ibn Kareem 为我工作,但我缺少一个例子,我也使用 Kotlin 和Mockito-Kotlin,所以我的解决方案是这样的:

    class Foo(var mutable: String)
    
    interface Bar {
        fun run(foo: Foo)
    }
    
    @Test fun `validate mutable parameter at invocation`() {
        val bar = mock<Bar>()
    
        var valueAtInvocation: String? = null
        whenever(bar.run(any())).then {
            val foo = it.arguments.first() as Foo
            valueAtInvocation = foo.mutable // Store mutable value as it was at the invocation
            Unit // The answer
        }
    
        val foo = Foo(mutable = "first")
        bar.run(foo)
        valueAtInvocation isEqualTo "first"
    
        foo.mutable = "second"
        bar.run(foo)
        valueAtInvocation isEqualTo "second"
    }
    

    valueAtInvocation 将表示最后一次调用bar.run(foo) 时可变属性foo.mutable 的值。也应该可以在 then {} 块内进行断言。

    【讨论】:

      【解决方案3】:

      您不能在不是mock 的对象上调用verify()。这是你的意思吗?

      Bar collaborator = mock(Bar.class); 
      Foo sut = spy(new Foo(collaborator));
      verify(collaborator).run(expectedList);
      

      【讨论】:

      • 谢谢,示例代码有错误,我更正了。不过,这不是我的问题。这是关于能够根据方法调用时的值验证参数,而不是最近的值。
      【解决方案4】:

      为什么不尝试使用参数捕获来获取预期列表运行时的值,然后进行比较。

      ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class);
      
      verify(collaborator).run(listCaptor.capture());
      
      assertEquals(expectedList, argument.getValue());
      

      【讨论】:

      • 如果你修改的列表是同一个实例,那么 argument.getValue() 将返回 expectedList 实例,而不是一个副本,所以这和他做的基本一样,不是吗?
      • @Michael Wiles 谢谢,但正如 jhericks 所提到的,ArgumentCaptor 捕获了原始 List 实例。
      • 抱歉,Michael,我对您的答案投了反对票,因为您的解决方案与 OP 的测试存在完全相同的问题,正如 jhericks 所解释的那样。
      猜你喜欢
      • 1970-01-01
      • 2017-02-18
      • 1970-01-01
      • 2021-07-24
      • 2011-08-24
      • 1970-01-01
      • 2011-08-23
      • 2012-04-12
      相关资源
      最近更新 更多