【问题标题】:Mocking the arguments passed to callbacks (lambdas)模拟传递给回调(lambda)的参数
【发布时间】:2018-06-26 09:47:07
【问题描述】:

如何使用 Mockito 模拟接受 lambda 的方法,以便能够控制将哪些参数传递给回调?我特别想模拟这样使用的JDBI method useExtension:

jdbi.useExtension(OrgUnitDao.class, dao -> {
    // Skip if already loaded
    // Skip if already loaded
    if (dao.orgUnitsAreLoaded()) {

我想将dao 对象替换回回调中,这样我就可以使用dao.orgUnitsAreLoaded() 的返回值来控制分支。

签名是这样的

public <E,X extends Exception> void useExtension(Class<E> extensionType,
                                             ExtensionConsumer<E,X> callback)
                                      throws NoSuchExtensionException,
                                             X extends Exception

【问题讨论】:

  • 我们不能只模拟界面ExtensionConsumer吗?
  • 这不对吧?这就是方法的用户传入的内容,我不想模拟回调(它包含我要测试的逻辑)。我想控制传入的E 类型参数到回调。所以我首先需要匹配参数,然后我可以使用 mock(OrgUnitDao.class) 和 when(mock.orgUnitsAreLoaded()) 调用回调
  • 您是否正在尝试测试您的回调,如果是,它与useExtension 方法无关,也许您需要将其重构为命名方法。
  • 这不是一个不合理的建议,但 l​​ambda 关闭了封闭范围内的许多变量。重构意味着传递很多我不想处理的状态。不过,我已经完成了一半......
  • lambda 函数或匿名类本来就是要简短的,如果你需要测试,那么可能它们太复杂了。

标签: java mocking mockito jdbi


【解决方案1】:

这是我问题的完整答案。它被简化为如何进行存根的基本知识,因此不反映我要测试的生产代码,但它准确地显示了执行此操作所需的机制。

final Jdbi jdbi = mock(Jdbi.class);
doAnswer(invocation -> {

    System.out.println("this is the doAnswer lambda - just setting up the answer and the mocks");

    final Class<OrgUnitDao> daoClass = invocation.getArgument(0);
    final ExtensionConsumer callback = invocation.getArgument(1);
    final OrgUnitDao mock1 = mock(daoClass);

    when(mock1.orgUnitsAreLoaded()).thenReturn(false);

    // call the actual callback method
    callback.useExtension(mock1);

    return null;

}).when(jdbi).useExtension(eq(OrgUnitDao.class), any());

// This is the method call I am to test
// Regard this as hidden away in some outer method in
// the System-Under-Test, but that I have been able
// to inject all its dependencies
jdbi.useExtension(OrgUnitDao.class, new Foo());

/// Further down, outside of the method

// Only replaced the lambda with this to get toString() for debugging ...
class Foo implements ExtensionConsumer<OrgUnitDao, RuntimeException> {
    @Override
    public void useExtension(OrgUnitDao orgUnitDao) throws RuntimeException {
        System.out.println("A real method call, now using the passed in mocked dao:" + orgUnitDao.orgUnitsAreLoaded());
    }

    @Override
    public String toString() {
        return "OrgUnitDao class";
    }
}

【讨论】:

  • 这是我不明白的事情,为什么不直接调用模拟的DAO。你想用Foo测试什么。
  • 这只是对如何进行实际存根的机制的指导。除了 Mockito 的 API,我的回答没有显示对任何东西的实际测试。 useExtension 方法(Foo 类)是我真正要测试的(它包含问题中提到的 sn-p) - 尽管 why 实际上与问题无关。问题不在于正确的设计或编写好的测试。这是关于语法和 Mockito API 的。
  • 好的,我以为您正在寻找一种方法来捕获 lambda 并调用参数而不重复实现代码。
【解决方案2】:

为了与"Calling callbacks with Mockito" 问题的对话并行,您的 lambda 可能会在您的被测方法执行期间同步调用,或者稍后可能会基于某些外部因素或交互来调用。与Dawood's answer there 一样,your answer here 使用 Mockito Answer 将起作用,如果您正在寻找同步样式(其中mockJdbi 在methodUnderTest 返回之前立即调用您的 lambda),这是唯一的方法。如果您的 lambda 将被异步调用,或者如果您的系统允许您异步调用 lambda,您可能希望在您的被测方法返回之后但 在交互之前测试状态与 lambda.

// MockitoJUnitRunner, MockitoRule, or MockitoAnnotations.initMocks populate these.
// Especially useful for the ArgumentCaptor's generic arguments.
@Mock Jdbi mockJdbi;
@Mock OrgUnitDao mockOrgUnitDao;
@Captor ArgumentCaptor<ExtensionConsumer<OrgUnitDao, RuntimeException>>
    extensionConsumerCaptor;

@Test public void yourTest() throws Exception {
  // No stubbing needed! Just create the system under test.
  YourSystemUnderTest systemUnderTest = new YourSystemUnderTest(mockJdbi);

  // Call the method under test, which presumably calls useExtension(...).
  systemUnderTest.methodUnderTest();

  // Assert anything that should be true before the lambda is called.
  assertFalse(systemUnderTest.getSomeState());

  // Confirm that useExtension was called, and simultaneously acquire the lambda.
  // ArgumentCaptor.capture() is a matcher, so every argument requires a matcher like eq.
  verify(mockJdbi).useExtension(eq(OrgUnitDao.class), extensionConsumerCaptor.capture());

  // Prepare the mock DAO and call the lambda.
  when(mockDao.getFoo()).thenReturn("bar");
  extensionConsumerCaptor.getValue().useExtension(mockDao);

  // Assert anything that should be true after the lambda is called.
  assertTrue(systemUnderTest.getSomeState());
}

虽然 lambda 减少了以前与匿名内部类相关联的样板,但您可能也更喜欢使用 Captor 样式,因为它使您免于创建冗长的 Answer 实现并在其中隐藏您的测试断言或 Mockito 验证。如果您的项目更喜欢具有清晰的“given-when-then”结构的 BDD 样式模拟,这尤其诱人(尽管我的示例更接近于“given-when-then-when-then”)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-09
    • 1970-01-01
    相关资源
    最近更新 更多