如何在 WebClient 单元测试中使用 MockWebServer 模拟错误响应

How to mock error response using MockWebServer in WebClient unit tests

我正在为使用 WebClient 调用 REST 端点的方法编写单元测试。 使用 MockWebServer,我能够覆盖成功响应的代码,但是 我找不到任何方法来模拟错误响应,以便与错误处理有关的相关代码也包含在单元测试中。

来源class:

@Service
@Slf4j
public class EmployeeService {

   private final WebClient webClient;
   private final PaymentServiceConfiguration paymentServiceConfiguration;
   
   public EmployeeService(WebClient webClient, PaymentServiceConfiguration paymentServiceConfiguration){
      this.webClient = webClient;
      this.paymentServiceConfiguration= paymentServiceConfiguration;

   }

   public Mono<PaymentDataResponse> getPaymentInfo(PaymentDataRequest paymentDataRequest){
      return webClient
             .post()
             .uri(paymentServiceConfiguration.getBaseUri() + "/payment")
             .body(Mono.just(paymentDataRequest, PaymentDataRequest.class)
             .retrieve()
             .onStatus(HttpStatus::isError, this.handleErrorResponse)
             .bodyToMono(PaymentDataResponse.class)
             .doOnError((throwable)-> {
                 log.error("Exception in getPaymentInfo method. Message {}", throwable.getMessage());
                 throw(Exceptions.propagate(throwable));
             });

   }

   private Mono<? extends Throwable> handleErrorResponse(ClientResponse clientResponse){
      Mono<String> errorMessage = clientResponse.bodyToMono(String.class);
      return errorMessage.flatMap((message) -> {
         log.error("Error response code is: {}, and the message is: {} ", clientResponse.rawStatusCode(), message);
         return Mono.error(new RuntimeException(message));
      });
   }

}

测试Class:

@RunWith(MockitoJunitRunner.class)
public class EmployeeServiceTest {
   private MockWebServer server;
   private EmployeeService employeeService;
   private PaymentServiceConfiguration paymentServiceConfiguration;
   private String paymentServiceUri;

   @Before
   public void setUp() throws IOException{
     paymentServiceConfiguration = mock(PaymentServiceConfiguration.class);
     paymentServiceUri = "/paymentdomain.com";
     employeeService = new EmployeeService(WebClient.create(), paymentServiceConfiguration);
     server = new MockWebServer();
     
     server.start();
   }

  @Test
  public testPaymentInfo() throws IOException, InterruptedException {
     when(paymentServiceConfiguration.getBaseUri()).thenReturn(server.url(paymentServiceUri).url.toString());

     String result = "{\"paymentId\": 101, "\paymentType\": \"online\"}";

     server.enque(
         new MockResponse.setResponseCode(200)
                 .setHeader("content-type", "application/json")
                 .setBody(result);
     );
     
     StepVerifier.create(employeeService.getPaymentInfo(new PaymentDataRequest())
          .expectNext(new ObjectMapper.readValue(result, PaymentDataResponse.class))
          .expectComplete()
          .verify();
     
     RecordRequest request = server.takeRequest();
     Assert.assertEquals("POST", request.getMethod());
     Assert.assertEquals("/paymentdomain.com/payment", request.getPath());
  }

  @After
  public void tearDown() throws IOException {
     server.shutdown();
  }
   
}

以上测试涵盖了代码的快乐路径,并适当标记了代码覆盖率。我怎样才能在这里模拟错误,以便它可以覆盖源代码中的以下几行 class 代码(关于错误场景)

// .onStatus(HttpStatus::isError, this.handleErrorResponse)

// .doOnError((throwable)-> { ......

您需要提供错误状态 4xx 或 5xxx。例如,

server.enqueue(
        new MockResponse().setResponseCode(500)
                .setHeader("content-type", "application/json")
                .setBody("{}");
);

StepVerifier.create(employeeService.getPaymentInfo(new PaymentDataRequest())
        .expectError()
        .verify();

ps

我建议查看 WireMock which provides a very good API for testing web clients matchig . You could test posittive and negative scanarios including timeouts, retry logic using Scenarios or even simulate timeouts using Delays