【问题标题】:How do I test Function's code when it's passed as method parameter?当函数作为方法参数传递时,如何测试函数的代码?
【发布时间】:2019-11-13 13:20:20
【问题描述】:

是否可以测试用 lambda 函数编写并在方法 process 内部传递的代码?

@AllArgsConstructor
public class JsonController {
    private final JsonElementProcessingService jsonElementProcessingService;
    private final JsonObjectProcessingService jsonObjectProcessingService;
    private final JsonArrayProcessingService jsonArrayProcessingService;

    public void process(String rawJson) {
        jsonElementProcessingService.process(json -> {
            JsonElement element = new JsonParser().parse(json);
            if (element.isJsonArray()) {
                return jsonArrayProcessingService.process(element.getAsJsonArray());
            } else {
                return jsonObjectProcessingService.process(element.getAsJsonObject());
            }
        }, rawJson);
    }
}

由于 lambda 是惰性的,所以当我调用 JsonController::process 时,不会调用函数 (Function::apply) 所以有什么方法可以检查 jsonArrayProcessingService::process 是否被调用?

@RunWith(JMockit.class)
public class JsonControllerTest {
    @Injectable
    private JsonElementProcessingService jsonElementProcessingService;
    @Injectable
    private JsonObjectProcessingService jsonObjectProcessingService;
    @Injectable
    private JsonArrayProcessingService jsonArrayProcessingService;
    @Tested
    private JsonController jsonController;

    @Test
    public void test() {
        jsonController.process("[{\"key\":1}]");
        // how check here that jsonArrayProcessingService was invoked?
    }
}

【问题讨论】:

  • 我不熟悉jmockit,但我认为在您的情况下正确的步骤是:“运行 UUT 方法,从模拟依赖项中获取注入的 lambda,运行它,验证,基于你传递的 json,正确的模拟被调用。”主要原因是您必须首先验证调用了正确的JsonElementProcessingService 方法,然后通过调用它传递的 lambda 执行正确的细化。

标签: java lambda jmockit


【解决方案1】:

只需将其转换为方法即可使其可测试(且可读):

public void process(String rawJson) {
    jsonElementProcessingService.process(this::parse, rawJson);
}

Object parse(String json) {
    JsonElement element = new JsonParser().parse(json);
    if (element.isJsonArray()) {
        return jsonArrayProcessingService.process(element.getAsJsonArray());
    } else {
        return jsonObjectProcessingService.process(element.getAsJsonObject());
    }
}

我个人遵循的相关指导原则是:

  • 每当我的 lambda 需要大括号时,将它们转换为方法
  • 组织代码以便可以进行单元测试

您可能需要更改 parse 方法的返回类型以匹配您的处理服务(您未显示)返回的任何内容。

【讨论】:

  • 实际上我确实需要使用传递函数的方法,因为它只有在某些条件适合时才会被调用。
  • @kbo 一个方法参考(我编码为this::parse一个Function。除了命名之外,它在所有方面都与 lambda 相同。
  • 您的示例公开了代码,据我了解,您建议直接测试方法解析,但这不是我想要的
  • 然后将其设为私有包(参见编辑后的代码)并在同一个包中使用测试对其进行测试。或者将其设为私有并使用 groovy/Spock 对其进行测试。这是测试代码的唯一(也是最好的)方法。
  • 我无法进行任何重构,所以问题仍然存在
【解决方案2】:

鉴于其相对基本的重定向逻辑,您是否只想确认哪个@Injectables 被调用了:

  @Test
  public void test() {
    jsonController.process("[{\"key\":1}]");

    new Verifications() {{
      jsonArrayProcessingService.process(withInstanceOf(JsonArray.class));
    }};
  }

【讨论】: