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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:36:20