SpringBoot测试中Mocked JPA Repository方法未返回预设值问题
Spring Boot单元测试中Mock仓储方法始终返回null问题排查
我定义了CalculatorService接口,其实现类包含核心业务逻辑,相关代码如下:
接口定义
public interface CalculatorService { LoanWeb calculateSimpleLoan(LoanWeb loanWeb) throws Exception; }
服务实现类
@Service @AllArgsConstructor public class CalculatorServiceImpl implements CalculatorService { private final LoanRepository loanRepository; // 继承JpaRepository private final PropertiesConfig propertiesConfig; private final CalculatorUtility calculatorUtility; private final LoanInfoRepository loanInfoRepository; @Override public LoanWeb calculateSimpleLoan(LoanWeb loanWeb) throws Exception { // 参数校验 if (loanWeb.getLoanTerm() == null || loanWeb.getLoanTerm() <= 0) throw new LeanPayException(ErrorCode.INVALID_INTEGER_ERROR.code, List.of("loanTerm")); var payments = someCalculation(); Loan loan = loanRepository.findFirstByAmountAndRateAndPaymentsAndFrequency (loanWeb.getLoanAmount(), loanWeb.getInterestRate(), payments, Frequency.MONTHLY); // 此处loan始终返回null,无法进入非空分支 if (loan != null) return LoanWeb.builder() .monthlyPayment(loan.getPmt()) .totalInterestPaid(loan.getLoanInfos().stream().mapToDouble(LoanInfo::getInterest).sum()).build(); } }
JPA仓储层定义
@Repository public interface LoanRepository extends JpaRepository<Loan, Long> { Loan findFirstByAmountAndRateAndPaymentsAndFrequency(Double amount, Double rate, Integer payments, Frequency frequency); }
测试类代码
@SpringBootTest(properties = {"spring.cloud.config.enabled: false", "logging.level.com.package.calculator: OFF"}, classes= {ObjectMapper.class, CalculatorServiceImpl.class, LoanRepository.class}) public class CalculatorServiceTest { @Autowired private ObjectMapper objectMapper; @Autowired private CalculatorService calculatorService; @MockBean(classes = LoanRepository.class) private LoanRepository loanRepository; @MockBean private PropertiesConfig propertiesConfig; @MockBean private CalculatorUtility calculatorUtility; @MockBean private LoanInfoRepository loanInfoRepository; @MockBean private TestService testService; private Loan loan1; // 从文件加载,确认非null @PostConstruct public void init() throws IOException{ String jsonString = IOUtils.toString( Objects.requireNonNull(this.getClass().getResourceAsStream("/json/PreExistingLoan.json")), StandardCharsets.UTF_8); this.loan1 = objectMapper.readValue(jsonString, Loan.class); } @Test void calculateSimpleLoanWithPreExistingLoan() throws LeanPayException { Mockito.doReturn(this.loan1).when(loanRepository).findFirstByAmountAndRateAndPaymentsAndFrequency( ArgumentMatchers.anyDouble(), ArgumentMatchers.anyDouble(), ArgumentMatchers.anyInt(), ArgumentMatchers.any(Frequency.class)); LoanWeb result = this.calculatorService.calculateSimpleLoan(LoanWeb.builder() .loanTerm(10).simpleLoanTerm(SimpleLoanTerm.MONTH).build()); Assertions.assertEquals(result.getTotalInterestPaid(), 10); Assertions.assertEquals(result.getMonthlyPayment(), 107.0); } }
问题现象
测试执行时应用上下文可正常启动,测试方法可运行,但服务层调用被Mock的仓储方法时始终返回null,无法进入预期业务分支,多次排查未定位根因。
根因与修复方案
按优先级逐个排查修复即可:
- Spring上下文加载配置冲突
你在@SpringBootTest的classes属性中手动指定了加载LoanRepository.class,同时又用@MockBean标注要Mock该Bean。Spring初始化上下文时会优先加载你指定的真实JPA动态代理Bean,@MockBean的替换逻辑未生效,最终注入到CalculatorServiceImpl中的不是你打了桩的Mock实例。
修复:将LoanRepository.class从@SpringBootTest的classes列表中移除,@MockBean会自动将生成的Mock对象注册到Spring上下文,无需提前加载真实类。 - Mockito参数匹配器使用错误
仓储方法的amount/rate是Double包装类型、payments是Integer包装类型,你打桩时用的anyDouble()/anyInt()是针对原始类型double/int的匹配器,无法匹配null值。而你构造测试入参LoanWeb时仅设置了loanTerm和simpleLoanTerm,loanAmount/interestRate默认值为null,直接导致参数匹配失败,打桩逻辑不触发,Mock对象返回默认值null。
修复:将原始类型匹配器替换为包装类型匹配器,即使用ArgumentMatchers.any(Double.class)、ArgumentMatchers.any(Integer.class)替代原有anyDouble()、anyInt();或者构造测试入参时给loanAmount、interestRate赋合法非null值。 - 依赖的Mock对象未打桩导致参数异常
服务逻辑中payments值来自someCalculation()调用,该方法依赖被@MockBean标注的CalculatorUtility实例,你未给该方法配置打桩规则,Mock对象默认返回null,最终传入仓储方法的payments参数为null,进一步导致参数匹配失败。
修复:在测试方法中补充对应打桩逻辑,示例:
返回值替换为你的业务预期值即可。Mockito.when(calculatorUtility.someCalculation(ArgumentMatchers.any())).thenReturn(10); - 测试数据有效性校验
可在测试方法执行前加一行断言Assertions.assertNotNull(loan1),确认JSON反序列化正常、测试数据确实非null,排除测试数据加载失败导致返回null的情况。
内容的提问来源于stack exchange,提问作者Wrapper
相关产品推荐
相关产品推荐

