如何使用 http 请求在 lambda 上对 whenCompleteAsync 进行单元测试?
How do I unit test whenCompleteAsync on a lambda with a http request?
我想为以下 class 创建一个单元测试:
@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);
我目前无法更改 class 的设计,只能对其进行测试。
如何模拟消费者对象以在 lambda 中生成代码 运行?
这甚至可能吗?
getSomething
方法存在竞争条件。无法可靠地测试它,因为它具有不确定的行为。
问题是 consumer
是在请求完成后异步调用的。 getSomething
中没有任何内容可以确保在 return someEntity1.getSomeField()
发生之前发生。这意味着它可能 return 从读取实体复制的字段,或者它可能 return 该字段的默认值。最有可能的是,它会在调用 consumer
之前 return (因为请求相对较慢)。请求完成后,它会设置 someEntity1
中的字段,但此时 getSomething
已经 return 向调用者提供了不正确的值,并且 someEntity1
引用的对象=] 不会再读了。
正确的处理方法是使 getSomething
也 return 成为 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());
}
然后,要对此进行单元测试,您可以像您一样为 WebTarget
、Invocation.Builder
、CompletionStageRxInvoker
和 Response
创建模拟。与其模拟 CompletionStage
,不如模拟 completionStageRxInvoker.get()
方法 return CompletableFuture.completedFuture(mockResponse)
更简单。请注意,CompletableFuture
是 CompletionStage
的具体实现,它是 JavaSE 的一部分。
更好的是,为了减少模拟的扩散,您可以重构它以将请求逻辑与响应处理逻辑分开。像这样:
public CompletionStage<String> getSomething(String inputField) {
return apiClient
.get(inputField, param, token)
.thenApply(SomeEntity::getSomeField);
}
其中 apiClient
是您可以模拟的自定义 class 或接口的注入实例,方法声明如下:
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));
}
我想为以下 class 创建一个单元测试:
@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);
我目前无法更改 class 的设计,只能对其进行测试。
如何模拟消费者对象以在 lambda 中生成代码 运行? 这甚至可能吗?
getSomething
方法存在竞争条件。无法可靠地测试它,因为它具有不确定的行为。
问题是 consumer
是在请求完成后异步调用的。 getSomething
中没有任何内容可以确保在 return someEntity1.getSomeField()
发生之前发生。这意味着它可能 return 从读取实体复制的字段,或者它可能 return 该字段的默认值。最有可能的是,它会在调用 consumer
之前 return (因为请求相对较慢)。请求完成后,它会设置 someEntity1
中的字段,但此时 getSomething
已经 return 向调用者提供了不正确的值,并且 someEntity1
引用的对象=] 不会再读了。
正确的处理方法是使 getSomething
也 return 成为 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());
}
然后,要对此进行单元测试,您可以像您一样为 WebTarget
、Invocation.Builder
、CompletionStageRxInvoker
和 Response
创建模拟。与其模拟 CompletionStage
,不如模拟 completionStageRxInvoker.get()
方法 return CompletableFuture.completedFuture(mockResponse)
更简单。请注意,CompletableFuture
是 CompletionStage
的具体实现,它是 JavaSE 的一部分。
更好的是,为了减少模拟的扩散,您可以重构它以将请求逻辑与响应处理逻辑分开。像这样:
public CompletionStage<String> getSomething(String inputField) {
return apiClient
.get(inputField, param, token)
.thenApply(SomeEntity::getSomeField);
}
其中 apiClient
是您可以模拟的自定义 class 或接口的注入实例,方法声明如下:
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));
}