Spring Boot如何测试分页返回结果及编写规范的Service层单元测试
现有测试写法存在的问题
- 核心逻辑错误:不应该Mock被测类
ProductServiceImpl本身的方法。你当前对getAllProducts和saveProduct的Mock操作,会导致完全不执行Service的真实业务代码,测试没有任何意义。你现在觉得save测试能通过纯属巧合,就算你删除repository.save(product)这行业务代码,测试依然会返回成功。 - 依赖Mock方向错误:分页测试中你注释掉的
when(productRepository.findProductByMerchantId(...))才是正确方向,我们只需要Mock Service的外部依赖ProductRepository的返回,不需要动被测类本身。 - 缺少必要断言:现有测试只验证了Repository方法被调用,没有验证Service返回的结果是否符合预期,也没有覆盖异常分支的场景。
- 逻辑验证不完整:没有校验传入的分页、排序参数是否被正确转换成
Pageable对象传递给Repository。
规范的Service层单元测试写法
Service层单元测试的核心原则是:只验证Service本身的业务逻辑,所有外部依赖(这里就是ProductRepository)全部用Mock对象替代,验证范围包括入参处理逻辑、依赖调用的参数正确性、返回值是否符合预期、异常是否正确抛出。
修正后的完整测试代码如下:
@ExtendWith(MockitoExtension.class) class ProductServiceImplTest { @Mock ProductRepository productRepository; @InjectMocks ProductServiceImpl productServiceImpl; @Test void getAllProducts_正常返回分页结果() { // 构造测试数据 Product product = new Product(); List<Product> productList = Collections.singletonList(product); Page<Product> mockPage = new PageImpl<>(productList); Long merchantId = 2L; Integer pageNo = 0; Integer pageSize = 2; String sortBy = "inventory"; // Mock依赖返回 when(productRepository.findProductByMerchantId(eq(merchantId), argThat(pageable -> pageable.getPageNumber() == pageNo && pageable.getPageSize() == pageSize && pageable.getSort().getOrderFor(sortBy) != null ))).thenReturn(Optional.of(mockPage)); // 调用被测方法 List<Product> result = productServiceImpl.getAllProducts(merchantId, pageNo, pageSize, sortBy); // 断言结果 assertNotNull(result); assertEquals(1, result.size()); assertEquals(product, result.get(0)); // 验证依赖调用次数 verify(productRepository, times(1)).findProductByMerchantId(any(Long.class), any(Pageable.class)); } @Test void getAllProducts_无对应商户产品时抛出异常() { // 构造入参 Long merchantId = 999L; // Mock返回空Optional触发异常 when(productRepository.findProductByMerchantId(eq(merchantId), any(Pageable.class))) .thenReturn(Optional.empty()); // 断言异常是否符合预期 IllegalStateException exception = assertThrows(IllegalStateException.class, () -> productServiceImpl.getAllProducts(merchantId, 0, 2, "inventory")); assertTrue(exception.getMessage().contains(merchantId.toString())); // 验证依赖调用 verify(productRepository, times(1)).findProductByMerchantId(eq(merchantId), any(Pageable.class)); } @Test void saveProduct_保存成功返回产品() { // 构造入参 Product product = new Product(); // 直接调用被测方法,不需要Mock被测类本身 Product result = productServiceImpl.saveProduct(product); // 断言结果 assertNotNull(result); assertEquals(product, result); // 验证Repository被正确调用 verify(productRepository, times(1)).save(product); } }
Repository接口是否需要测试
需要,但不属于单元测试范畴,属于集成测试:
- Repository层是直接和数据库交互的层,需要启动Spring上下文,连接测试数据库(一般用H2内存数据库降低测试成本)来验证自定义查询的逻辑正确性。
- 你定义的
findProductByMerchantId的分页、排序逻辑是否正确,只有集成测试可以验证,不要把这部分测试耦合到Service层的单元测试中,两者测试边界要清晰。
内容的提问来源于stack exchange,提问作者Kamran Memmedzade
相关产品推荐
相关产品推荐

