使用 Mockito,如何验证我的 lambda 表达式被调用



我正在尝试测试一种采用Consumer函数的方法,我想与 Mockito 验证我的 lambda 表达式是否只调用了一次。我现在使用的是在最终的单元素数组上使用标志的那种笨拙的方式:

final boolean[] handlerExecuted = {false};
instance.conditionalRun(item -> {
  handlerExecuted[0] = true;
  item.foo();
});
Assert.assertTrue(
    "Handler should be executed.",
    handlerExecuted[0]);

似乎应该有更好的方法(也许是使用Mockito间谍(来验证此lambda表达式是否只被调用过一次。

其他一些答案提供了完全按照我想要的方式的替代方案,但这可以通过监视Consumer类本身并让间谍调用您真正想要执行的方法来实现。包装 lambda 以创建间谍的帮助程序方法在这里有所帮助:

/** Get a spied version of the given Consumer. */
private Consumer<Item> itemHandlerSpy(Consumer<Item> itemHandler) {
  // Create a spy of the Consumer functional interface itself.
  @SuppressWarnings("unchecked")
  Consumer<Item> spy = (Consumer<Item>) Mockito.spy(Consumer.class);
  // Tell the spy to run the given consumer when the Consumer is given something to consume. 
  Mockito.doAnswer(it -> {
    // Get the first (and only) argument passed to the Consumer.
    Item item = (Item) it.getArguments()[0];
    // Pass it to the real consumer so it gets processed.
    itemHandler.accept(item);
    return null;
  }).when(spy).accept(Mockito.any(Item.class));
  return spy;
}

然后测试方法变得非常简单:

Consumer<Item> itemHandler = itemHandlerSpy(Item::foo);
instance.conditionalRun(itemHandler);
// This verifies conditionalRun called the Consumer exactly once.
Mockito.verify(itemHandler).accept(Mockito.any(Item.class));

您可以验证您的方法是否被调用,任何 lambda 表达式都像这样调用:

    verify(someClass).myMethodThatExpectsALambda(any())
    private fun <T> any(): T {
        Mockito.any<T>()
        return null as T
    }

您始终可以将 lambda 表达式替换为匿名内部类 - 或正确接口的模拟(消费者(。然后,您可以像往常一样验证您的模拟。

您的方法还可以通过将boolean[]替换为int[]来验证是否只调用一次,例如:

final int[] calls = new int[1];
instance.conditionalRun(item -> {
    calls[0]++;
    item.foo();
});
Assert.assertEquals(1, calls[0]);

注意:使用mockito更复杂,因为您稍后调用item.foo()

编辑

了想之后,我发现您的测试测试导致测试的多个行为很复杂,有关更多详细信息,您可以看到为什么单元测试只测试一件事?,并且测试至少可以分开如下,因为我看到了您的测试代码的意图:

@Test
void runOnceWhenConditionIsSatisfied() throws Throwable {
    final int[] calls = new int[1];
    instance.conditionalRun(item -> calls[0]++);
    Assert.assertEquals(1, calls[0]);
}
@Test
void anotherTest() throws Throwable {
    instance.conditionalRun(item -> {
        item.foo();
    });
    //... testing your SUT here 
}

然后,您很乐意使用模拟将状态验证替换为行为验证,如下所示。

@Test
void runOnceWhenConditionIsSatisfied() throws Throwable {
    Consumer<T> it= Mockito.mock(Consumer.class);
    //       ^--- T is the type of item
    instance.conditionalRun(it);
    Mockito.verify(it,Mockito.only()).accept(Mockito.any());
    // using any to ignore matching the parameters ---^
}

that takes a Consumer function - 好的...,但是如何将Object传递给该消费者?Consumer是一种返回void并将item作为输入的方法。因此,您可以item传递给方法(作为参数(,也可以在方法中创建它。无论哪种方式,您都需要模拟item并断言它只调用foo一次,而不是 lambda 表达式。

由于 lambda 表达式被去糖化为方法(使用编译器生成的名称(,我真的想不出一种方法来模拟它 - 毕竟你需要嘲笑这个名字。如果您使用的方法引用可能根本不会被编译器去糖,事情会更复杂。

相关内容

  • 没有找到相关文章

最新更新