当前位置: 首页 > 知识库问答 >
问题:

如何模拟Spring WebFlux WebClient?

林炫明
2023-03-14

我们编写了一个小型Spring Boot REST应用程序,它在另一个RESTendpoint上执行REST请求。

@RequestMapping("/api/v1")
@SpringBootApplication
@RestController
@Slf4j
public class Application
{
    @Autowired
    private WebClient webClient;

    @RequestMapping(value = "/zyx", method = POST)
    @ResponseBody
    XyzApiResponse zyx(@RequestBody XyzApiRequest request, @RequestHeader HttpHeaders headers)
    {
        webClient.post()
            .uri("/api/v1/someapi")
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromObject(request.getData()))
            .exchange()
            .subscribeOn(Schedulers.elastic())
            .flatMap(response ->
                    response.bodyToMono(XyzServiceResponse.class).map(r ->
                    {
                        if (r != null)
                        {
                            r.setStatus(response.statusCode().value());
                        }

                        if (!response.statusCode().is2xxSuccessful())
                        {
                            throw new ProcessResponseException(
                                    "Bad status response code " + response.statusCode() + "!");
                        }

                        return r;
                    }))
            .subscribe(body ->
            {
                // Do various things
            }, throwable ->
            {
                // This section handles request errors
            });

        return XyzApiResponse.OK;
    }
}

我们是Spring新手,在为这个小代码段编写单元测试时遇到了困难。

是否有一种优雅的(反应式)方式来模拟webClient本身或启动webClient可以用作endpoint的模拟服务器?

共有3个答案

杭永安
2023-03-14

OkHttp团队可以使用MockWebServer。基本上,Spring团队也将其用于测试(至少他们在这里是这样说的)。以下是一个参考源的示例

根据Tim的博客文章,让我们考虑一下我们有以下服务:

class ApiCaller {
    
   private WebClient webClient;
    
   ApiCaller(WebClient webClient) {
      this.webClient = webClient;
   }
    
   Mono<SimpleResponseDto> callApi() {
       return webClient.put()
                       .uri("/api/resource")
                       .contentType(MediaType.APPLICATION_JSON)
                       .header("Authorization", "customAuth")
                       .syncBody(new SimpleRequestDto())
                       .retrieve()
                       .bodyToMono(SimpleResponseDto.class);
    }
}

然后可以按照以下方式设计测试(与origin相比,我改变了异步链在Reactor中使用StepVerifier进行测试的方式):

class ApiCallerTest {
  
  private final MockWebServer mockWebServer = new MockWebServer();
  private final ApiCaller apiCaller = new ApiCaller(WebClient.create(mockWebServer.url("/").toString()));
  
  @AfterEach
  void tearDown() throws IOException {
     mockWebServer.shutdown();
  }
  
  @Test
  void call() throws InterruptedException {
       mockWebServer.enqueue(new MockResponse().setResponseCode(200)
                                               .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                                               .setBody("{\"y\": \"value for y\", \"z\": 789}")
      );
      
      //Asserting response
      StepVerifier.create(apiCaller.callApi())
                  .assertNext(res -> {
                        assertNotNull(res);
                        assertEquals("value for y", res.getY());
                        assertEquals("789", res.getZ());
                  })
                  .verifyComplete();
 
     //Asserting request
     RecordedRequest recordedRequest = mockWebServer.takeRequest();
     //use method provided by MockWebServer to assert the request header
     recordedRequest.getHeader("Authorization").equals("customAuth");
     DocumentContext context = >JsonPath.parse(recordedRequest.getBody().inputStream());
     //use JsonPath library to assert the request body
     assertThat(context, isJson(allOf(
            withJsonPath("$.a", is("value1")),
            withJsonPath("$.b", is(123))
     )));
  }
}
厍胤运
2023-03-14

使用以下方法,可以使用Mockito模拟WebClient进行以下调用:

webClient
.get()
.uri(url)
.header(headerName, headerValue)
.retrieve()
.bodyToMono(String.class);

webClient
.get()
.uri(url)
.headers(hs -> hs.addAll(headers));
.retrieve()
.bodyToMono(String.class);

模拟方法

private static WebClient getWebClientMock(final String resp) {
    final var mock = Mockito.mock(WebClient.class);
    final var uriSpecMock = Mockito.mock(WebClient.RequestHeadersUriSpec.class);
    final var headersSpecMock = Mockito.mock(WebClient.RequestHeadersSpec.class);
    final var responseSpecMock = Mockito.mock(WebClient.ResponseSpec.class);

    when(mock.get()).thenReturn(uriSpecMock);
    when(uriSpecMock.uri(ArgumentMatchers.<String>notNull())).thenReturn(headersSpecMock);
    when(headersSpecMock.header(notNull(), notNull())).thenReturn(headersSpecMock);
    when(headersSpecMock.headers(notNull())).thenReturn(headersSpecMock);
    when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
    when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull()))
            .thenReturn(Mono.just(resp));

    return mock;
}
韦鸣
2023-03-14

我们通过提供一个自定义的ExchangeFunction来实现这一点,该函数只需将我们想要的响应返回给WebClient Builder:


webClient = WebClient.builder()
            .exchangeFunction(clientRequest -> 
                    Mono.just(ClientResponse.create(HttpStatus.OK)
                    .header("content-type", "application/json")
                    .body("{ \"key\" : \"value\"}")
                    .build())
            ).build();

myHttpService = new MyHttpService(webClient);

Map<String, String> result = myHttpService.callService().block();

// Do assertions here
    

如果我们想使用Mokcito来验证是否进行了调用或在类中的多个单元测试中重用WebClient,我们还可以模拟交换函数:

@Mock
private ExchangeFunction exchangeFunction;

@BeforeEach
void init() {
    WebClient webClient = WebClient.builder()
            .exchangeFunction(exchangeFunction)
            .build();

    myHttpService = new MyHttpService(webClient);
}

@Test
void callService() {
    when(exchangeFunction.exchange(any(ClientRequest.class)))
   .thenReturn(buildMockResponse());
    Map<String, String> result = myHttpService.callService().block();

    verify(exchangeFunction).exchange(any());

    // Do assertions here
}
    

注意:如果在调用时得到与发布者相关的空指针异常,则IDE可能已导入Mono。而不是Mockito。当

来源:

  • WebClient javadoc
 类似资料:
  • CacheController.Somemethod();但是当submit方法被称为submit方法时,它会创建一个线程和cacheController.somemethod();从不调用测试类。

  • 当我运行测试时,我可以做什么来更改时间

  • null 我看过其他几个类似的问题,但没有一个有帮助: 模拟包含对SQLiteOpenHelper引用的类时出错 使用mockito库在java中模拟最终类-我在导入PowerMock时遇到了很多问题 如何用mockito模拟最后一个类-我已经添加了依赖项,并用行创建了文件,正如答案中所建议的那样,我仍然得到同样的错误。我还尝试了建议的答案,但这给了我一个‘没有足够的信息来推断类型变量t'erro

  • 我的要求是测试这段代码,更重要的是测试序列化器,因此,给出一个JSON片段,我如何测试所有的值都正确地通过商人的实例。 我不知道RestTemplate使用哪个序列化器将JSON序列化为对象。

  • 我知道Dan North设计BDD的意图之一是将词汇表从复杂的测试域中移开。然而,在实现由外到内的方法时,我们似乎仍然需要对模仿行为(或者,如果您愿意的话)有一些了解。North在这个视频中建议,如果我从最外层的域对象开始,然后向内工作,我会在发现合作者时模仿它们,然后用适当的实现替换它们。所以最后,我以一组端到端测试结束。 Martin Fowler在这篇博客文章中定义了TDD的两个阵营:“古典

  • 问题内容: 在我的测试案例中,我需要测试时间敏感的方法,在该方法中,我们使用的是Java 8类LocalDate,而 不是Joda 。 运行测试时,我该如何更改时间? 问题答案: 在您的代码中,将替换为。 然后,您可以通过生产并使用固定时钟进行测试。 这是一个例子: 首先,注入。如果您使用的是Spring Boot,请执行以下操作: 其次,输入您的代码: 现在,在单元测试类中:

  • 它返回null,而不是预期的客户端,对象类的工作正常。我只想写测试。我是漏掉了什么还是做错了测试?谢谢你的指导。

  • 问题内容: 在我的代码中,我有这样的东西: 如何在junit测试中“模拟”它以返回特定日期? 问题答案: 就我所知,您有三个明智的选择: 将实例插入您当天设置的任何方法/类中。 使用JodaTime而不是。这不是一个选择,而是一个建议,因为JodaTime将使您的生活更加轻松。您仍然需要将此时间注入该方法中。 包装在一些界面中,可以让您获取时间。然后,您只需模拟该接口并使其返回常量即可。