Spring Webflux Mockito - 模拟 Webclient 调用的响应
Spring Webflux Mockito - mock the response of a Webclient call
关于如何在单元测试阶段“强制”或“模拟”Webclient http 调用的响应的小问题。
我有一个非常简单的方法:
public String question() {
String result = getWebClient().mutate().baseUrl(someUrlVariable).build().post().uri("/test").retrieve().bodyToMono(String).block();
if (result == null) {
doSomething1();
}
if (result.equals("")) {
doSomething2();
}
if (result.equals("foo")) {
doSomething3();
}
如您所见,此方法的复杂部分是 Webclient 调用。它有(在本例中)7 个 .method(),如 .mutate()、.post()。等...
在我的用例中,我根本没有兴趣测试这个 Webclient。
我希望 Mockito 的功能在某种程度上相当于:
public String question() {
// it is just unit test. Mockito, please just return me the string I tell you to return please. Don't even execute this next line if possible, just return me this dummy response
String result = the-thing-I-tell-mockito-to-return;
if (result == null) {
doSomething1();
}
if (result.equals("")) {
doSomething2();
}
if (result.equals("foo")) {
doSomething3();
}
到目前为止,我尝试了整行的 Mockito doNothing()
或 Mockito.when(getWebclient()... )
加上 .thenReturn,但没有成功。
请问如何实现?
您必须首先确保 getWebclient()
return 是模拟。根据您现有的代码示例,我无法判断这是针对不同的 class 还是私有方法(通过构造函数注入 WebClient
或 WebClient.Builder
可能有意义) .
接下来,您必须使用 Mockito 模拟整个方法链。这几乎包括 copy/pasting 您的整个实施:
when(webClient.mutate()).thenReturn(webClient);
when(webClient.baseUrl(yourUrl)).thenReturn(...);
// etc.
Mockito 可以 return 深度存根(检查 documentation 并搜索 RETURN_DEEP_STUBS
)可以简化这个存根设置。
但是,对于您的 WebClient
测试和模拟 HTTP 响应,更好的 解决方案是 to spawn a local HTTP server。这涉及较少的 Mockito 仪式,还允许测试错误场景(不同的 HTTP 响应、缓慢的响应等),
I would like to avoid those copy/pasting of when()
好吧,你已经设计了你的代码,所以测试它的唯一方法是复制粘贴 when
。
那你是怎么设计的呢?好吧,您将 API 代码与逻辑混合在一起,这是您不应该做的事情。编写测试时首先要考虑的是“我要测试什么?”,答案通常是业务逻辑。
如果我们查看您的代码:
public String question() {
// This is api code, we dont want to test this,
// spring has already tested this for us.
String result = getWebClient()
.mutate()
.baseUrl(someUrlVariable)
.build()
.post()
.uri("/test")
.retrieve()
.bodyToMono(String)
.block();
// This is logic, this is want we want to test
if (result == null) {
doSomething1();
}
if (result.equals("")) {
doSomething2();
}
if (result.equals("foo")) {
doSomething3();
}
}
当我们设计一个应用程序时,我们将它分成几层,通常是一个前端 api (RestController),然后是中间的业务逻辑 (Controllers),最后是调用其他的不同资源 apis(存储库、资源等)
因此,当涉及到您的应用程序时,我会重新设计它,拆分 api 和逻辑:
@Bean
@Qualifier("questionsClient")
public WebClient webclient(WebClient.Builder webClient) {
return webClient.baseUrl("https://foobar.com")
.build();
}
// This class responsibility is to fetch, and do basic validation. Ensure
// That whatever is returned from its functions is a concrete value.
// Here you should handle things like basic validation and null.
@Controller
public class QuestionResource {
private final WebClient webClient;
public QuestionResource(@Qualifier("questionsClient") WebClient webClient) {
this.webClient = webClient;
}
public String get(String path) {
return webClient.post()
.uri(path)
.retrieve()
.bodyToMono(String)
.block();
}
}
// In this class we make business decisions on the values we have.
// If we get a "Foo" we do this. If we get a "Bar" we do this.
@Controller
public class QuestionHandler {
private final QuestionResource questionResource;
public QuestionResource(QuestionResource questionResource) {
this.questionResource = questionResource;
}
public String get() {
final String result = questionResource.get("/test");
// also i dont see how the response can be null.
// Null should never be considered a value and should not be let into the logic.
// Because imho. its a bomb. Anything that touches null will explode (NullPointerException).
// Null should be handled in the layer before.
if (result == null) {
return doSomething1();
}
if (result.equals("")) {
return doSomething2();
}
if (result.equals("foo")) {
return doSomething3();
}
}
}
那么在你的测试中:
@Test
public void shouldDoSomething() {
final QuestionResource questionResourceMock = mock(QuestionResource.class);
when(questionResourceMock.get("/test")).thenReturn("");
final QuestionHandler questionHandler = new QuestionHandler(questionResourceMock);
final String something = questionHandler.get();
// ...
// assert etc. etc.
}
此外,我建议您不要改变网络客户端,为每个网络客户端创建一个 api,因为它很快就会变得混乱。
本文没有IDE,因此可能存在编译错误等
关于如何在单元测试阶段“强制”或“模拟”Webclient http 调用的响应的小问题。
我有一个非常简单的方法:
public String question() {
String result = getWebClient().mutate().baseUrl(someUrlVariable).build().post().uri("/test").retrieve().bodyToMono(String).block();
if (result == null) {
doSomething1();
}
if (result.equals("")) {
doSomething2();
}
if (result.equals("foo")) {
doSomething3();
}
如您所见,此方法的复杂部分是 Webclient 调用。它有(在本例中)7 个 .method(),如 .mutate()、.post()。等...
在我的用例中,我根本没有兴趣测试这个 Webclient。
我希望 Mockito 的功能在某种程度上相当于:
public String question() {
// it is just unit test. Mockito, please just return me the string I tell you to return please. Don't even execute this next line if possible, just return me this dummy response
String result = the-thing-I-tell-mockito-to-return;
if (result == null) {
doSomething1();
}
if (result.equals("")) {
doSomething2();
}
if (result.equals("foo")) {
doSomething3();
}
到目前为止,我尝试了整行的 Mockito doNothing()
或 Mockito.when(getWebclient()... )
加上 .thenReturn,但没有成功。
请问如何实现?
您必须首先确保 getWebclient()
return 是模拟。根据您现有的代码示例,我无法判断这是针对不同的 class 还是私有方法(通过构造函数注入 WebClient
或 WebClient.Builder
可能有意义) .
接下来,您必须使用 Mockito 模拟整个方法链。这几乎包括 copy/pasting 您的整个实施:
when(webClient.mutate()).thenReturn(webClient);
when(webClient.baseUrl(yourUrl)).thenReturn(...);
// etc.
Mockito 可以 return 深度存根(检查 documentation 并搜索 RETURN_DEEP_STUBS
)可以简化这个存根设置。
但是,对于您的 WebClient
测试和模拟 HTTP 响应,更好的 解决方案是 to spawn a local HTTP server。这涉及较少的 Mockito 仪式,还允许测试错误场景(不同的 HTTP 响应、缓慢的响应等),
I would like to avoid those copy/pasting of when()
好吧,你已经设计了你的代码,所以测试它的唯一方法是复制粘贴 when
。
那你是怎么设计的呢?好吧,您将 API 代码与逻辑混合在一起,这是您不应该做的事情。编写测试时首先要考虑的是“我要测试什么?”,答案通常是业务逻辑。
如果我们查看您的代码:
public String question() {
// This is api code, we dont want to test this,
// spring has already tested this for us.
String result = getWebClient()
.mutate()
.baseUrl(someUrlVariable)
.build()
.post()
.uri("/test")
.retrieve()
.bodyToMono(String)
.block();
// This is logic, this is want we want to test
if (result == null) {
doSomething1();
}
if (result.equals("")) {
doSomething2();
}
if (result.equals("foo")) {
doSomething3();
}
}
当我们设计一个应用程序时,我们将它分成几层,通常是一个前端 api (RestController),然后是中间的业务逻辑 (Controllers),最后是调用其他的不同资源 apis(存储库、资源等)
因此,当涉及到您的应用程序时,我会重新设计它,拆分 api 和逻辑:
@Bean
@Qualifier("questionsClient")
public WebClient webclient(WebClient.Builder webClient) {
return webClient.baseUrl("https://foobar.com")
.build();
}
// This class responsibility is to fetch, and do basic validation. Ensure
// That whatever is returned from its functions is a concrete value.
// Here you should handle things like basic validation and null.
@Controller
public class QuestionResource {
private final WebClient webClient;
public QuestionResource(@Qualifier("questionsClient") WebClient webClient) {
this.webClient = webClient;
}
public String get(String path) {
return webClient.post()
.uri(path)
.retrieve()
.bodyToMono(String)
.block();
}
}
// In this class we make business decisions on the values we have.
// If we get a "Foo" we do this. If we get a "Bar" we do this.
@Controller
public class QuestionHandler {
private final QuestionResource questionResource;
public QuestionResource(QuestionResource questionResource) {
this.questionResource = questionResource;
}
public String get() {
final String result = questionResource.get("/test");
// also i dont see how the response can be null.
// Null should never be considered a value and should not be let into the logic.
// Because imho. its a bomb. Anything that touches null will explode (NullPointerException).
// Null should be handled in the layer before.
if (result == null) {
return doSomething1();
}
if (result.equals("")) {
return doSomething2();
}
if (result.equals("foo")) {
return doSomething3();
}
}
}
那么在你的测试中:
@Test
public void shouldDoSomething() {
final QuestionResource questionResourceMock = mock(QuestionResource.class);
when(questionResourceMock.get("/test")).thenReturn("");
final QuestionHandler questionHandler = new QuestionHandler(questionResourceMock);
final String something = questionHandler.get();
// ...
// assert etc. etc.
}
此外,我建议您不要改变网络客户端,为每个网络客户端创建一个 api,因为它很快就会变得混乱。
本文没有IDE,因此可能存在编译错误等