1. 概述

本文将探讨如何使用 MockServer 为同一请求模拟多个响应。

MockServer 通过模拟真实 API 的行为来模拟它们,使我们能够在不需要后端服务的情况下测试应用程序。

2. 应用程序设置

让我们考虑一个支付处理 API,它提供了一个处理支付请求的端点。当发起支付时,此 API 调用外部银行支付服务。银行的 API 响应一个引用 paymentId。使用此 ID,API 通过轮询银行的 API 定期检查支付状态,确保支付成功处理。

首先,让我们定义支付请求模型,其中包含处理支付所需的卡详细信息:

public record PaymentGatewayRequest(
  String cardNumber, String expiryMonth, String expiryYear, String currency, int amount, String cvv) {
}

类似地,让我们定义支付响应模型,其中包含支付状态:

public record PaymentGatewayResponse(UUID id, PaymentStatus status) {
    public enum PaymentStatus {
        PENDING,
        AUTHORIZED,
        DECLINED,
        REJECTED
    }
}

现在,让我们添加控制器和实现来与银行的支付服务集成,用于提交支付和状态轮询。当支付状态开始为待处理,后来更新为 AUTHORIZEDDECLINEDREJECTED 时,API 将继续轮询:

@PostMapping("payment/process")
public ResponseEntity<PaymentGatewayResponse> submitPayment(@RequestBody PaymentGatewayRequest paymentGatewayRequest) 
  throws JSONException {
    String paymentSubmissionResponse = webClient.post()
      .uri("http://localhost:9090/payment/submit")
      .body(BodyInserters.fromValue(paymentGatewayRequest))
      .retrieve()
      .bodyToMono(String.class)
      .block();

    UUID paymentId = UUID.fromString(new JSONObject(paymentSubmissionResponse).getString("paymentId"));
    PaymentGatewayResponse.PaymentStatus paymentStatus = PaymentGatewayResponse.PaymentStatus.PENDING;
    while (paymentStatus.equals(PaymentGatewayResponse.PaymentStatus.PENDING)) {
        String paymentStatusResponse = webClient.get()
          .uri("http://localhost:9090/payment/status/%s".formatted(paymentId))
          .retrieve()
          .bodyToMono(String.class)
          .block();
        paymentStatus = PaymentGatewayResponse.PaymentStatus.
          valueOf(new JSONObject(paymentStatusResponse).getString("paymentStatus"));
        logger.info("Payment Status {}", paymentStatus);
    }
    return new ResponseEntity<>(new PaymentGatewayResponse(paymentId, paymentStatus), HttpStatus.OK);
}

为了测试此 API 并确保它轮询支付状态直到达到终止状态,我们需要能够模拟支付状态 API 的多个响应。模拟响应应该最初几次返回 PENDING 状态,然后再更新为 AUTHORIZED,使我们能够有效验证轮询机制。

3. 如何为同一请求模拟多个响应

测试此 API 的第一步是在端口 9090 上启动模拟服务器。我们的 API 使用此端口与银行的支付提交和状态服务交互:

class PaymentControllerTest {
    private ClientAndServer clientAndServer;

    private final MockServerClient mockServerClient = new MockServerClient("localhost", 9090);

    @BeforeEach
    void setup() {
        clientAndServer = startClientAndServer(9090);
    }
    
    @AfterEach
    void tearDown() {
        clientAndServer.stop();
    }

    // ...
}

接下来,让我们为支付提交端点设置模拟以返回 paymentId

mockServerClient
  .when(request()
    .withMethod("POST")
    .withPath("/payment/submit"))
  .respond(response()
    .withStatusCode(200)
    .withBody("{\"paymentId\": \"%s\"}".formatted(paymentId))
    .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));

要为同一请求模拟多个响应,我们需要使用 Times 类与 when() 方法一起使用。

when() 方法使用 Times 参数来指定请求应该匹配的次数。这允许我们为重复请求模拟不同的响应。

接下来,让我们模拟支付状态端点以返回 4 次 PENDING 状态:

mockServerClient
  .when(request()
    .withMethod("GET")
    .withPath("/payment/status/%s".formatted(paymentId)), Times.exactly(4))
  .respond(response()
    .withStatusCode(200)
    .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .withBody("{\"paymentStatus\": \"%s\"}"
    .formatted(PaymentGatewayResponse.PaymentStatus.PENDING.toString())));

接下来,让我们模拟支付状态端点返回 AUTHORIZED

mockServerClient
  .when(request()
    .withMethod("GET")
    .withPath("/payment/status/%s".formatted(paymentId)))
  .respond(response()
    .withStatusCode(200)
    .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .withBody("{\"paymentStatus\": \"%s\"}"
    .formatted(PaymentGatewayResponse.PaymentStatus.AUTHORIZED.toString())));

最后,让我们向支付处理 API 端点发送请求以接收 AUTHORIZED 结果:

webTestClient.post()
  .uri("http://localhost:9000/api/payment/process")
  .bodyValue(new PaymentGatewayRequest("4111111111111111", "12", "2025", "USD", 10000, "123"))
  .exchange()
  .expectStatus()
  .isOk()
  .expectBody(PaymentGatewayResponse.class)
  .value(response -> {
      Assertions.assertNotNull(response);
      Assertions.assertEquals(PaymentGatewayResponse.PaymentStatus.AUTHORIZED, response.status());
  });

我们应该看到日志打印四次 "Payment Status PENDING",然后是 "Payment Status AUTHORIZED"。

4. 总结

在本教程中,我们探讨了如何为同一请求模拟多个响应,使用 Times 类实现 API 的灵活测试。

MockServerClient 中的默认 when() 方法使用 Times.unlimited() 来一致地响应所有匹配的请求。要为特定数量的请求模拟响应,我们可以使用 Times.exactly()


原始标题:How to Mock Multiple Responses for the Same Request | Baeldung