首页 > 解决方案 > 如何在带有 http 请求的 lambda 上对 whenCompleteAsync 进行单元测试?

问题描述

我想为以下类创建一个单元测试:

@Service
public class XService{
    
    public String getSomething(String inputField) {
        final SomeEntity someEntity1 = new SomeEntity();
        final AtomicReference<Throwable> throwable = new AtomicReference<>();

        BiConsumer<Response, Throwable> consumer = (response, error) -> {
        if (error != null) {
            throwable.set(error);
        } else {
            SomeEntity someEntity2 = response.readEntity(SomeEntity.class);
            someEntity1.setSomeField(someEntity2.getSomeField());
            //does some stuff with the response
        }
        };
        
        WebTarget target = client.target("api_url"+inputField);
        target.queryParam("param", param)
            .request(MediaType.APPLICATION_JSON)
            .acceptLanguage(Locale.ENGLISH)
            .header("Authorization", token)
            .rx()
            .get()
            .whenCompleteAsync(consumer);

        return someEntity1.getSomeField();
    }
}

我已经嘲笑了一切,直到.whenCompleteAsync(consumer)使用这样的东西:

when(mockWebTarget.queryParam(any(),any())).thenReturn(mockWebTarget);
CompletionStageRxInvoker completionStageRxInvoker = mock(CompletionStageRxInvoker.class);
when(mockBuilder.rx()).thenReturn(completionStageRxInvoker);
CompletionStage<Response> mockResp = mock(CompletionStage.class);
when(completionStageRxInvoker.get()).thenReturn(mockResp);

我目前无法更改课程的设计,只能对其进行测试。

如何模拟消费者对象以使代码在 lambda 内运行?这甚至可能吗?

标签: javalambdajunitmockitocompletion-stage

解决方案


getSomething方法具有竞争条件。不可能可靠地测试它,因为它具有不确定的行为。

问题是consumer在请求完成后异步调用。没有任何东西getSomething可以确保在发生之前return someEntity1.getSomeField()发生。这意味着它可能会返回从读取实体复制的字段,或者可能会返回该字段的默认值。最有可能的是,它会在consumer被调用之前返回(因为请求相对较慢)。一旦请求完成,它会设置 中的字段someEntity1,但是此时,getSomething已经向调用者返回了错误的值,并且someEntity1不会再次读取 所引用的对象。

处理此问题的正确方法是getSomething同时返回 a CompletionStage

public CompletionStage<String> getSomething(String inputField) {
    WebTarget target = client.target("api_url"+inputField);
    return target.queryParam("param", param)
        .request(MediaType.APPLICATION_JSON)
        .acceptLanguage(Locale.ENGLISH)
        .header("Authorization", token)
        .rx()
        .get()
        .thenApply(response -> response.readEntity(SomeEntity.class).getSomeField());
}

然后,要对此进行单元测试,您可以为WebTargetInvocation.BuilderCompletionStageRxInvokerResponse像您所拥有的那样创建模拟。与模拟相比,返回模拟的方法CompletionStage会更简单。请注意,它的具体实现是 JavaSE 的一部分。completionStageRxInvoker.get()CompletableFuture.completedFuture(mockResponse)CompletableFutureCompletionStage

更好的是,为了减少模拟的扩散,您可以重构它以将请求逻辑与响应处理逻辑分开。像这样的东西:

public CompletionStage<String> getSomething(String inputField) {
    return apiClient
            .get(inputField, param, token)
            .thenApply(SomeEntity::getSomeField);
}

您可以模拟的自定义类或接口的注入实例在哪里apiClient,其方法声明如下:

public CompletionStage<SomeEntity> get(String inputField, Object param, String token) {
    WebTarget target = client.target("api_url"+inputField);
    return target.queryParam("param", param)
            .request(MediaType.APPLICATION_JSON)
            .acceptLanguage(Locale.ENGLISH)
            .header("Authorization", token)
            .rx()
            .get()
            .thenApply(response -> response.readEntity(SomeEntity.class));
}

推荐阅读