我可以操纵模拟匹配器的顺序吗?

时间:2019-03-21 17:49:46

标签: mockito matcher

某些上下文

在模拟上设置模拟(when或验证调用(verify)时,Mockito要求您要么提供模拟方法需要的所有具体值,要么为其提供匹配器。无法混合使用这些样式。

when(mock.method(1, 2, 3));
when(mock.method(eq(1), eq(2), eq(3)));

我正在谈论第二种风格。

由于Mockito的工作方式,匹配器的调用顺序很重要。在内部,Mockito会将匹配器注册在堆栈上,并在必要时按顺序执行它们。

我想要实现的目标

我想编写一些可与Mockito结合使用的测试实用程序。我希望这些实用程序方法将调用委派给模拟,并插入一些默认的匹配器,否则它们将成为样板测试代码。

例如:

public String callOnMock(int argument2) {
    return mock.call(eq(1), argument2, argThat(i -> i >= 3));
}

将被这样使用:

when(callOnMock(eq(2)).thenReturn("result");

问题

这不起作用,因为Mockito以错误的顺序注册了这些匹配器:

  1. eq(2)
  2. eq(1)
  3. argThat(i -> i >= 3)

应该是

  1. eq(1)
  2. eq(2)
  3. argThat(i -> i >= 3)

我是否可以操纵那些匹配器的注册顺序?

我现在org.mockito.AdditionalMatchers拥有操纵内部堆栈以允许匹配器组合的方法(andornot),至少在内部是在Mockito内部核心是可能的。

是否还可以显式弹出和推送匹配器?

3 个答案:

答案 0 :(得分:1)

使用Supplier

public String callOnMock(Supplier<Integer> argument2) {
    return mock.call(eq(1), argument2.get(), argThat(i -> i >= 3));
}

when(callOnMock(() -> eq(2)).thenReturn("result");

答案 1 :(得分:0)

尝试一下:

public String callOnMock(int argument2) {
    return mock.call(eq(1), eq(argument2), argThat(i -> i >= 3));
}

并这样称呼它:

when(callOnMock(2)).thenReturn("result");

答案 2 :(得分:0)

我认为有两种方法可以实现所需的行为。

1。在堆栈上操纵匹配器的顺序

  

这不是要走的路!

matcherStack似乎在Mockito内部。
他们确实有一个从堆栈中pullLocalizedMatchers的方法和一个reportMatcher方法来将ArgumentMatcher推入堆栈。这些可以通过

进行访问
org.mockito.internal.progress.ThreadSafeMockingProgress
    .mockingProgress()
    .getArgumentMatcherStorage()

因此,从理论上讲,您可以选择此路径,但是解决方案比较脆弱,因为您正在弄乱Mockito的内部。在后续版本的Mockito中,它们可能会更改,恕不另行通知。

幸运的是,有两种选择。

2。控制匹配器首先注册的顺序

使用Java 8 Supplier功能接口(对应于@ToYonos给出的this answer

Mockito在调用创建它们的方法时自动注册它们(eqargThatanyisNotNull,...)。但是您可以通过为每个匹配器传递一个Supplier来延迟调用这些方法。然后,便捷方法控制着执行这些供应商的顺序。

public String callOnMock(Supplier<Integer> argument2) {
    return mock.call(eq(1), argument2.get(), argThat(i -> i >= 3));
}

when(callOnMock(() -> eq(2))).thenReturn("result");

使用它看起来与普通的Mockito样式有些不同。

  

由于相同的问题,如果您为使用/汇总其他匹配器的供应商提供便捷的方法,则需要特别注意。

     
callOnMock(() -> AdditionalMatchers.and(isNotNull(), eq(2)))
     

将起作用,
  但这不会:

     
public Supplier<Integer> and(int matcher1, int matcher2){
   return () -> AdditionalMatchers.and(matcher1, matcher2);
}

callOnMock(and(isNotNull(), eq(2)))

这给您的方法的用户带来一些责任。他们必须确保没有匹配者被意外呼叫。

3。控制模拟游戏期望匹配者的顺序

将模拟调用委派给其他模拟对象可以让您控制参数的顺序。
您将必须定义一个接口,该接口按便捷方法接收到匹配器的顺序排列它们,最后将便捷方法添加的匹配器放在后面。
必须对该委托接口有所期望。

public interface MockDelegate {
    String call(Integer i1, Integer i0, Integer i2);
}

@Mock
private MockDelegate delegate;

@Before
public void setUp() {
    when(mock.call(any(), any(), any()))
            .thenAnswer(invocation -> delegate.call(
                    invocation.getArgument(1), // this delegates the call
                    invocation.getArgument(0), // but flips the first two arguments
                    invocation.getArgument(2)
            ));
}

public String callOnMock(int argument2) {
    return delegate.call(argument2, eq(1), argThat(i -> i >= 3));
}

这可以与普通的Mockito样式匹配器一起使用:

when(callOnMock(eq(2))).thenReturn("result");