Mockito可以在方法调用时根据它们的值验证参数吗?

问题描述:

我有一个Foo类是SUT和Bar类,这是它的合作者。 FooBar上调用run(List<Object> values),将“expectedList”作为参数。然后,Foo将为此List添加几个元素,以使其状态与调用run()时的状态不同。这是我的测试案例。Mockito可以在方法调用时根据它们的值验证参数吗?

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

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

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

+0

是的,大卫是对的。由于Mockito API的制作方式,不可能使用相同的参数引用来验证多个调用。 EasyMock可以这样做,因为它在生产代码运行之前有一个期望阶段。无论如何,我不是使用'Answer',而是使用'ArgurmentCaptor'并在该列表的最后状态中写入一个或多个断言,即使用FEST-Assert' assertThat(captor.getValue())。contains(“A”,“B “).contains(”T“,”U“);' – Brice 2012-02-01 13:33:09

+0

@Brice - 这与Michael Wiles的方法有什么不同? – 2012-02-01 20:28:13

+0

它不是。这只是实现测试目的的一种不同方式。因为大多数时候你并不需要检查中间参数,而只是发生了一些相互作用和最终结果。虽然我必须说,如果汤姆有具体的要求,然后同意这不会帮助他,但在这种情况下,我会避免我的生产代码中的可变对象。感觉两个协作者和消息之间的消息应该永远是不可变的。 – Brice 2012-02-02 15:15:16

对于不是mock的对象,不能调用verify()。这是你的意思吗?

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

谢谢,示例代码有一个错误,我纠正它。那不是我的问题。这是关于能够根据方法调用时的值验证参数,而不是最新的。 – 2012-01-31 23:34:23

为什么不尝试使用参数捕获获取期望列表的值,当它运行,然后你可以比较它。

ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class); 

verify(collaborator).run(listCaptor.capture()); 

assertEquals(expectedList, argument.getValue()); 
+6

如果你修改过的列表是同一个实例,那么argument.getValue()将返回'expectedList'实例,而不是副本,所以这和本质上是一样的,不是吗? – jhericks 2012-01-31 21:47:40

+0

@Michael Wiles谢谢,但正如jhericks所提到的,ArgumentCaptor捕获了原始的List实例。 – 2012-01-31 23:31:07

+0

对不起,迈克尔,我低估了你的答案,因为你的解决方案与OP的测试有完全相同的问题,正如jhericks所解释的那样。 – 2012-02-01 00:33:25

The answer of Dawood ibn Kareem为我工作,但我缺少一个例子,也是我用科特林和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将在最后调用代表可变属性foo.mutable的价值bar.run(foo)。也应该可以在then {}区块内做出断言。