[英]Spring Webflux Mockito - mock the response of a Webclient call
Small question regarding how to "force" or "mock" the response of a Webclient http call during unit test phase please.关于如何在单元测试阶段“强制”或“模拟”Webclient http 调用的响应的小问题。
I have a very straightforward method which does:我有一个非常简单的方法:
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();
}
As you can see, the complex part of this method is the Webclient call.如您所见,此方法的复杂部分是 Webclient 调用。 It has (in this example) 7.method() like.mutate(), .post().它有(在这个例子中)7.method() like.mutate(), .post()。 etc... ETC...
In my use case, I am not that interested testing this Webclient, at all.在我的用例中,我根本没有兴趣测试这个 Webclient。
What I would like to have with Mockito, is somehow the equivalent of:我希望 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();
}
So far, I tried Mockito doNothing(), or Mockito.when(getWebclient()... ) of the entire line plus.thenReturn, but no luck.到目前为止,我尝试了整行 plus.thenReturn 的 Mockito doNothing() 或 Mockito.when(getWebclient()... ),但没有运气。
How to achieve such please?请问如何实现?
Thank you谢谢
You have to first ensure that getWebclient()
returns a mock.您必须首先确保getWebclient()
返回一个模拟。 Based on your existing code example I can't tell if that's coming for a different class or is a private method (it might make sense to inject the WebClient
or WebClient.Builder
via the constructor).根据您现有的代码示例,我无法判断这是针对不同的 class 还是私有方法(通过构造函数注入WebClient
或WebClient.Builder
可能有意义)。
Next, you have to mock the whole method chain with Mockito.接下来,您必须使用 Mockito 模拟整个方法链。 This includes almost copy/pasting your entire implementation:这包括几乎复制/粘贴您的整个实现:
when(webClient.mutate()).thenReturn(webClient);
when(webClient.baseUrl(yourUrl)).thenReturn(...);
// etc.
Mockito can return deep stubs (check the documentation and search for RETURN_DEEP_STUBS
) that could simplify this stubbing setup. Mockito 可以返回可以简化此存根设置的深度存根(检查文档并搜索RETURN_DEEP_STUBS
)。
However, A better solution would be to spawn a local HTTP server for your WebClient
test and mock the HTTP responses.但是,更好的解决方案是为您的WebClient
测试生成本地 HTTP 服务器并模拟 HTTP 响应。 This involves less Mockito ceremony and also allows testing error scenarios (different HTTP responses, slow responses, etc.),这涉及较少的 Mockito 仪式并且还允许测试错误场景(不同的 HTTP 响应、慢响应等),
I would like to avoid those copy/pasting of when()我想避免复制/粘贴 when()
Well you have designed your code so that the only way to test it is by copy pasting of when
.好吧,您已经设计了代码,因此测试它的唯一方法是复制粘贴when
。
So how have you designed it?那么你是如何设计的呢? well you have mixed API-code with logic which is something you should not do.好吧,您将 API 代码与逻辑混合在一起,这是您不应该做的事情。 The first thing you need to think about when writing tests is " What is it i want to test? " and the answer is usually Business logic .编写测试时首先需要考虑的是“我要测试什么? ”而答案通常是业务逻辑。
If we look at your code:如果我们查看您的代码:
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();
}
}
When we design an application, we divide it into layers, usually a front facing api (RestController), then the business logic in the middle (Controllers) and lastly different resources that call other apis (repositories, resources etc.)当我们设计一个应用程序时,我们将它分为几层,通常是前面的 api(RestController),然后是中间的业务逻辑(Controllers),最后是调用其他 API 的不同资源(存储库、资源等)。
So when it comes to your application i would redesign it, split up the api and the logic:因此,当涉及到您的应用程序时,我会重新设计它,将 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();
}
}
}
Then in your test:然后在你的测试中:
@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.
}
Also, i suggest you don't mutate webclients, create one webclient for each api because it gets messy fast.另外,我建议你不要改变 webclients,为每个 api 创建一个 webclient,因为它很快就会变得混乱。
This is written without an IDE, so there might be compile errors etc. etc.这是在没有 IDE 的情况下编写的,因此可能存在编译错误等。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.