在本文中,我们将探讨如何使用 MockServer 模拟同一请求的多个响应。
MockServer通过模仿真实 API 的行为来模拟它们,使我们能够测试应用程序而无需后端服务。
我们探讨了如何模拟同一请求的多个响应,以便使用Times类灵活地测试 API。MockServerClient中的默认when()方法使用Times.unlimited()来一致地响应所有匹配的请求。要模拟特定数量请求的响应,我们可以使用Times.exactly()。
应用程序设置
让我们考虑一个支付处理 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 } }
|
现在,让我们添加控制器和实现,以便与银行的支付服务集成,以提交付款和状态轮询。当付款状态从待处理状态开始,然后更新为AUTHORIZED、DECLINED或REJECTED时, 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,使我们能够有效地验证轮询机制。
如何模拟同一请求的多个响应
测试此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()); });
|
我们应该看到日志打印四次“付款状态待定”,然后打印“付款状态已授权”。