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

Spring 6.1 RestClient的JUnit5 Mock测试示例及跨测试复用咨询

Spring 6.1 RestClient的JUnit5 Mock测试与Mock复用方案

一、ProductServiceClient的JUnit5 Mock测试示例

可以利用Spring提供的MockRestServiceServer拦截RestClient的HTTP请求,模拟服务端响应,完成对ProductServiceClient的单元测试。

测试代码示例

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
class ProductServiceClientTest {

    @Autowired
    private ProductServiceClient productServiceClient;

    @Autowired
    private RestClient restClient;

    private MockRestServiceServer mockServer;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @BeforeEach
    void setUp() {
        // 创建Mock服务端并绑定到当前RestClient实例
        mockServer = MockRestServiceServer.createServer(restClient);
    }

    @Test
    void getProductPrices_shouldReturnExpectedPriceList() throws Exception {
        // 准备测试参数与预期返回数据
        UUID testProductId = UUID.randomUUID();
        LocalDate testDate = LocalDate.of(2024, 6, 1);
        List<ProductPriceDto> expectedPrices = List.of(
                new ProductPriceDto("PRICE-001", 99.99),
                new ProductPriceDto("PRICE-002", 149.99)
        );

        // 配置Mock服务端的请求匹配规则与响应
        mockServer.expect(requestTo("/products/" + testProductId + "/prices/?date=" + testDate))
                .andExpect(method(HttpMethod.GET))
                .andRespond(withSuccess(
                        objectMapper.writeValueAsString(expectedPrices),
                        MediaType.APPLICATION_JSON
                ));

        // 调用客户端方法并验证结果
        List<ProductPriceDto> actualPrices = productServiceClient.getProductPrices(testProductId, testDate);
        assertThat(actualPrices).isEqualTo(expectedPrices);

        // 验证Mock服务端是否收到预期请求
        mockServer.verify();
    }
}

注意事项

  • 确保项目引入spring-boot-starter-test依赖,该依赖包含MockRestServiceServer所需类。
  • 若ProductPriceDto包含日期等特殊字段,需保证ObjectMapper能正确序列化/反序列化,可按需配置自定义ObjectMapper Bean。

二、在其他调用该客户端的测试中实现Mock复用

当多个测试类依赖ProductServiceClient时,可通过以下方式复用Mock逻辑,避免重复编码:

方式1:复用Mock配置类

创建通用测试配置类,定义Mock版本的ProductServiceClient并预设通用行为,其他测试类直接导入该配置即可。

通用Mock配置类

import org.mockito.Mockito;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import java.util.List;
import java.util.UUID;
import java.time.LocalDate;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@TestConfiguration
public class ProductServiceClientMockConfig {

    @Bean
    @Primary // 优先使用该Mock Bean,覆盖真实客户端实例
    public ProductServiceClient mockProductServiceClient() {
        ProductServiceClient mockClient = Mockito.mock(ProductServiceClient.class);
        
        // 预设通用Mock行为,比如默认返回空列表
        when(mockClient.getProductPrices(any(UUID.class), any(LocalDate.class)))
                .thenReturn(List.of());
        
        return mockClient;
    }
}

在其他测试类中复用

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.Import;
import java.util.List;
import java.util.UUID;
import java.time.LocalDate;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@Import(ProductServiceClientMockConfig.class) // 导入通用Mock配置
class OrderServiceTest {

    @Autowired
    private OrderService orderService; // 依赖ProductServiceClient的业务服务

    @Autowired
    private ProductServiceClient mockProductServiceClient;

    @Test
    void calculateOrderTotal_shouldUseMockedProductPrices() {
        // 根据当前测试需求,覆盖通用Mock行为
        UUID productId = UUID.randomUUID();
        LocalDate orderDate = LocalDate.now();
        List<ProductPriceDto> mockedPrices = List.of(new ProductPriceDto("PRICE-001", 99.99));
        when(mockProductServiceClient.getProductPrices(productId, orderDate))
                .thenReturn(mockedPrices);

        // 调用业务方法并断言结果
        double total = orderService.calculateTotal(productId, orderDate);
        assertThat(total).isEqualTo(99.99);
    }
}

方式2:基于Mockito的父类复用逻辑

创建抽象父类,完成ProductServiceClient的Mock初始化与通用行为预设,子类测试直接继承即可。

抽象父类

import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import java.util.List;
import java.util.UUID;
import java.time.LocalDate;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
public abstract class BaseBusinessTest {

    @Mock
    protected ProductServiceClient productServiceClient;

    @BeforeEach
    void setUpBaseMock() {
        // 预设通用Mock行为
        when(productServiceClient.getProductPrices(any(UUID.class), any(LocalDate.class)))
                .thenReturn(List.of());
    }
}

子类测试类

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.util.List;
import java.util.UUID;
import java.time.LocalDate;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;

class InventoryServiceTest extends BaseBusinessTest {

    @InjectMocks
    private InventoryService inventoryService; // 依赖ProductServiceClient的业务服务

    @Test
    void checkStockAvailability_shouldUseMockedPrices() {
        // 覆盖通用Mock行为,设置当前测试所需返回值
        UUID productId = UUID.randomUUID();
        List<ProductPriceDto> testPrices = List.of(new ProductPriceDto("PRICE-001", 59.99));
        when(productServiceClient.getProductPrices(productId, LocalDate.now()))
                .thenReturn(testPrices);

        // 执行测试并断言
        boolean isAvailable = inventoryService.isProductAvailable(productId);
        assertThat(isAvailable).isTrue();
    }
}

方式3:使用Spring Profile区分Mock环境

为测试环境创建专属Profile,在该Profile下加载Mock版本的ProductServiceClient,测试类通过激活Profile复用Mock。

带Profile的Mock配置

import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import java.util.List;
import java.util.UUID;
import java.time.LocalDate;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@Configuration
@Profile("test") // 仅在test Profile下生效
public class TestProductServiceClientConfig {

    @Bean
    public ProductServiceClient productServiceClient() {
        ProductServiceClient mockClient = Mockito.mock(ProductServiceClient.class);
        when(mockClient.getProductPrices(any(UUID.class), any(LocalDate.class)))
                .thenReturn(List.of());
        return mockClient;
    }
}

在测试类中激活Profile

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import java.util.List;
import java.util.UUID;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@ActiveProfiles("test") // 激活test Profile,加载Mock配置
class PaymentServiceTest {

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private ProductServiceClient mockProductServiceClient;

    @Test
    void processPayment_shouldCalculateCorrectAmount() {
        UUID productId = UUID.randomUUID();
        List<ProductPriceDto> mockedPrices = List.of(new ProductPriceDto("PRICE-001", 199.99));
        when(mockProductServiceClient.getProductPrices(productId, LocalDate.now()))
                .thenReturn(mockedPrices);

        boolean result = paymentService.processPayment(productId, 1);
        assertThat(result).isTrue();
    }
}

内容的提问来源于stack exchange,提问作者Undertaker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:05:59