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
}
}
现在,让我们添加控制器和实现来与银行的支付服务集成,用于提交支付和状态轮询。当支付状态开始为待处理,后来更新为 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
,使我们能够有效验证轮询机制。
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()
。