繁体   English   中英

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() like.mutate(), .post()。 ETC...

在我的用例中,我根本没有兴趣测试这个 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();
   }

到目前为止,我尝试了整行 plus.thenReturn 的 Mockito doNothing() 或 Mockito.when(getWebclient()... ),但没有运气。

请问如何实现?

谢谢

您必须首先确保getWebclient()返回一个模拟。 根据您现有的代码示例,我无法判断这是针对不同的 class 还是私有方法(通过构造函数注入WebClientWebClient.Builder可能有意义)。

接下来,您必须使用 Mockito 模拟整个方法链。 这包括几乎复制/粘贴您的整个实现:

when(webClient.mutate()).thenReturn(webClient);
when(webClient.baseUrl(yourUrl)).thenReturn(...);
// etc. 

Mockito 可以返回可以简化此存根设置的深度存根(检查文档并搜索RETURN_DEEP_STUBS )。

但是,更好的解决方案是为您的WebClient测试生成本地 HTTP 服务器并模拟 HTTP 响应。 这涉及较少的 Mockito 仪式并且还允许测试错误场景(不同的 HTTP 响应、慢响应等),

我想避免复制/粘贴 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),最后是调用其他 API 的不同资源(存储库、资源等)。

因此,当涉及到您的应用程序时,我会重新设计它,将 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.
}

另外,我建议你不要改变 webclients,为每个 api 创建一个 webclient,因为它很快就会变得混乱。

这是在没有 IDE 的情况下编写的,因此可能存在编译错误等。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM