简体   繁体   English

Spring Webflux Mockito - 模拟 Webclient 调用的响应

[英]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 还是私有方法(通过构造函数注入WebClientWebClient.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.

相关问题 Spring Webflux 如何为 WebClient Junit 模拟响应为 Mono.error - Spring Webflux how to Mock response as Mono.error for WebClient Junit Spring WebFlux WebClient:接收部分响应 - Spring WebFlux WebClient: receive part of response Spring webflux webclient 在评估第一次呼叫的响应时再次拨打电话 - Spring webflux webclient make another call while evaluating response from first call 使用 ArgumentsMatchers 和 Mockito 模拟类类型参数 - Mongo 聚合 Spring Webflux - Mock a class type argument with ArgumentsMatchers and Mockito - Mongo aggregation Spring Webflux 如何在@Service 中模拟 Spring @Autowired WebClient 响应? - How to mock Spring @Autowired WebClient response in @Service? Spring WebFlux - 为什么我必须等待 WebClient 响应? - Spring WebFlux - Why I have to wait for WebClient response? 使用Spring 3.2和Mockito模拟Web服务的响应 - Mock the response of a webservice using Spring 3.2 and Mockito 如何在 Spring Webflux 中测试一次性类型调用的 webclient - How to test webclient of Disposable type call in Spring Webflux Webflux - 全局转换 WebClient 响应 - Webflux - Transform a WebClient response globally Spring WebFlux WebClient timeout()和exchange() - Spring WebFlux WebClient timeout() and exchange()
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM