You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试调用外部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)

  1. 定义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
        );
    }
}
  1. 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());
    }
}
  1. 单元测试代码:
@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集成测试示例

  1. 添加WireMock依赖(Maven):
<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock-jre8</artifactId>
    <version>2.35.0</version>
    <scope>test</scope>
</dependency>
  1. 集成测试代码:
@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 21:55:26