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能正确序列化/反序列化,可按需配置自定义ObjectMapperBean。
二、在其他调用该客户端的测试中实现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
相关产品推荐
相关产品推荐

