如何模擬同一請求的多個回應
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 的多個回應。模擬回應最初應在更新為AUTHORIZED
之前多次返回PENDING
狀態,以便我們能夠有效地驗證輪詢機制。
3. 如何模擬同一請求的多個回應
測試此 API 的第一步是在連接埠 9090 上啟動模擬伺服器。
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
參數來指定請求應符合的次數。這使我們能夠模擬重複請求的不同回應。
接下來,讓我們模擬支付狀態端點以返回PENDING
狀態 4 次:
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().
與往常一樣,範例的原始程式碼可在 GitHub 上取得。