如何测试调用外部Payment REST API的SpringBoot BasketController?
测试BasketController并Mock外部Payment REST API
核心结论
当然可以Mock外部Payment REST服务,测试BasketController主要分为**单元测试(Mock依赖层)和集成测试(模拟外部API)**两种场景,下面分别给出实现方案和代码示例。
一、单元测试:Mock Payment服务的调用客户端
假设你的BasketController依赖一个PaymentService类,该类负责调用外部Payment API。单元测试中我们可以直接Mock这个PaymentService,不需要实际发起HTTP请求。
技术栈示例(Spring Boot + Mockito)
- 定义
PaymentService接口及实现:
public interface PaymentService { PaymentResponse processPayment(PaymentRequest request); } @Service public class RestPaymentService implements PaymentService { private final RestTemplate restTemplate; public RestPaymentService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Override public PaymentResponse processPayment(PaymentRequest request) { return restTemplate.postForObject( "https://external-payment-api.com/pay", request, PaymentResponse.class ); } }
- BasketController核心代码:
@RestController @RequestMapping("/basket") public class BasketController { private final PaymentService paymentService; public BasketController(PaymentService paymentService) { this.paymentService = paymentService; } @PostMapping("/checkout") public ResponseEntity<BasketCheckoutResponse> checkout(@RequestBody BasketCheckoutRequest request) { // 处理购物篮逻辑 PaymentRequest paymentRequest = mapToPaymentRequest(request); PaymentResponse paymentResponse = paymentService.processPayment(paymentRequest); if (paymentResponse.isSuccess()) { return ResponseEntity.ok(new BasketCheckoutResponse("SUCCESS", "支付完成")); } else { return ResponseEntity.badRequest().body(new BasketCheckoutResponse("FAILED", paymentResponse.getMessage())); } } private PaymentRequest mapToPaymentRequest(BasketCheckoutRequest request) { return new PaymentRequest(request.getTotalAmount(), request.getPaymentMethod()); } }
- 单元测试代码:
@WebMvcTest(BasketController.class) public class BasketControllerUnitTest { @Autowired private MockMvc mockMvc; @MockBean private PaymentService paymentService; @Test void checkout_WhenPaymentSucceeds_ReturnsSuccess() throws Exception { // Mock支付成功响应 PaymentResponse mockSuccessResponse = new PaymentResponse(true, null); when(paymentService.processPayment(any(PaymentRequest.class))) .thenReturn(mockSuccessResponse); // 发起结账请求 BasketCheckoutRequest request = new BasketCheckoutRequest(100.0, "CREDIT_CARD"); mockMvc.perform(post("/basket/checkout") .contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString(request))) // 验证响应 .andExpect(status().isOk()) .andExpect(jsonPath("$.status").value("SUCCESS")) .andExpect(jsonPath("$.message").value("支付完成")); } @Test void checkout_WhenPaymentFails_ReturnsBadRequest() throws Exception { // Mock支付失败响应 PaymentResponse mockFailResponse = new PaymentResponse(false, "余额不足"); when(paymentService.processPayment(any(PaymentRequest.class))) .thenReturn(mockFailResponse); // 发起结账请求 BasketCheckoutRequest request = new BasketCheckoutRequest(1000.0, "CREDIT_CARD"); mockMvc.perform(post("/basket/checkout") .contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString(request))) // 验证响应 .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.status").value("FAILED")) .andExpect(jsonPath("$.message").value("余额不足")); } }
二、集成测试:模拟外部Payment API
如果需要验证完整的HTTP调用链路(比如请求参数是否正确传递),可以用WireMock模拟外部Payment服务的行为。
WireMock集成测试示例
- 添加WireMock依赖(Maven):
<dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock-jre8</artifactId> <version>2.35.0</version> <scope>test</scope> </dependency>
- 集成测试代码:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class BasketControllerIntegrationTest { @Autowired private TestRestTemplate restTemplate; private WireMockServer wireMockServer; @BeforeEach void setup() { wireMockServer = new WireMockServer(9090); wireMockServer.start(); // 配置RestTemplate指向WireMock地址 System.setProperty("payment.api.url", "http://localhost:9090"); } @AfterEach void teardown() { wireMockServer.stop(); } @Test void checkout_WhenPaymentApiReturnsSuccess_ReturnsSuccess() { // 模拟外部API成功响应 wireMockServer.stubFor(post(urlEqualTo("/pay")) .withHeader("Content-Type", equalTo(MediaType.APPLICATION_JSON_VALUE)) .withRequestBody(matchingJsonPath("$.amount", equalTo("100.0"))) .willReturn(aResponse() .withStatus(200) .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"success\": true, \"message\": null}"))); // 调用结账接口 BasketCheckoutRequest request = new BasketCheckoutRequest(100.0, "CREDIT_CARD"); ResponseEntity<BasketCheckoutResponse> response = restTemplate.postForEntity( "/basket/checkout", request, BasketCheckoutResponse.class ); // 验证响应 assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody().getStatus()).isEqualTo("SUCCESS"); // 验证WireMock收到正确请求 wireMockServer.verify(postRequestedFor(urlEqualTo("/pay")) .withRequestBody(matchingJsonPath("$.amount", equalTo("100.0")))); } @Test void checkout_WhenPaymentApiReturnsFailure_ReturnsBadRequest() { // 模拟外部API失败响应 wireMockServer.stubFor(post(urlEqualTo("/pay")) .willReturn(aResponse() .withStatus(400) .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) .withBody("{\"success\": false, \"message\": \"余额不足\"}"))); // 调用结账接口 BasketCheckoutRequest request = new BasketCheckoutRequest(1000.0, "CREDIT_CARD"); ResponseEntity<BasketCheckoutResponse> response = restTemplate.postForEntity( "/basket/checkout", request, BasketCheckoutResponse.class ); // 验证响应 assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertThat(response.getBody().getStatus()).isEqualTo("FAILED"); assertThat(response.getBody().getMessage()).isEqualTo("余额不足"); } }
关键注意点
- 单元测试聚焦Controller逻辑,快速验证分支场景,无需关心外部API细节。
- 集成测试模拟真实HTTP交互,确保请求参数、响应解析等链路正确,适合端到端流程验证。
- 两种测试都要覆盖成功、失败、异常等多种场景。
内容的提问来源于stack exchange,提问作者serah
相关产品推荐
相关产品推荐

