使用Mockito模拟服务调用时遭遇NullPointerException问题求助
Mockito模拟调用返回NullPointerException排查与修复
问题场景
使用Mockito模拟ReportingManagerClient的refreshReportJobLease方法时,实际调用返回null,触发NullPointerException,导致测试失败。
功能代码(CompanionTask.java)
public void run() { try { String jobLeaseTimeoutString = header.get(JOB_LEASE_TIMEOUT).get(0); Instant jobLeaseTimeout = Instant.parse(jobLeaseTimeoutString); // 注意:此处变量名错误,jobLeaseTime未定义,应为jobLeaseTimeout if (jobLeaseTime != null) { if (isJobLeaseExpiring(jobLeaseTimeout)) { ResponseEntity<ReportJob> reply = ctx.getReportingManagerClient().refreshReportJobLease(ctx.getJob().getId()); jobLeaseTimeoutString = reply.getHeaders().get(JOB_LEASE_TIMEOUT).get(0); } else { sleep(); } } } catch (Exception e) { // 缺少异常处理,可能导致问题扩散 } }
测试代码(原存在问题版本)
@MockBean protected ReportingManagerIntegration reportingManagerClient; @MockBean protected NamedParameterJdbcTemplate jdbcTemplate; @Test public void testCompanionTask() { ReportJob reportJob = generateReportJob(); QueryContext ctx = QueryContext.builder() .jdbc(jdbcTemplate) .busy(new AtomicInteger()) .reportingManagerClient(reportingManagerClient) .job(reportJob) .build(); ReportJob reportJobMock = ReportJob.builder() .createdAt(Instant.now()) .expiresAt(Instant.now().plusSeconds(60)) // 原代码缺少右括号 .build(); // 重复定义变量+语法错误 Instant leaseRenewTimeout = Instant.now().plusSeconds(10); // 拼写错误:ResonseEntity -> ResponseEntity ResponseEntity responseEntity = ResponseEntity.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) // 变量未定义:leaseTimeout -> leaseRenewTimeout .header(JOB_LEASE_TIMEOUT, leaseRenewTimeout.toString()) .body(reportJobMock); // 语法错误:Instant.now(0.plusSeconds(300) -> Instant.now().plusSeconds(300) Instant leaseRenewTimeout = Instant.now().plusSeconds(300); // 拼写错误:contentTyoe -> contentType ResponseEntity<ReportJob> responseReportJob = ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .header(JOB_LEASE_TIMEOUT, leaseRenewTimeout.toString()) .body(reportJobMock); // 冗余的Mock语句,且可能因ctx内部实例不一致导致失效 when(ctx.getReportingManagerClient().refreshReportJobLease(anyString())).thenReturn(responseReportJob); when(reportingManagerClient.refreshReportJobLease(anyString())).thenReturn(responseReportJob); CompanionTask task = new CompanionTask(ctx, responseEntity.getHeaders()); task.run(); }
核心问题与修复方案
1. 语法/拼写错误导致Mock逻辑失效
原测试代码存在大量笔误和语法错误,直接导致Mock的ResponseEntity无法正确创建,或测试代码编译失败,Mock规则根本没被执行。
修复点:
- 修正类名拼写:
ResonseEntity→ResponseEntity - 修正方法名拼写:
contentTyoe→contentType - 修复变量定义错误:删除重复的
leaseRenewTimeout定义,修正Instant.now()的语法错误 - 修正变量引用错误:
leaseTimeout→leaseRenewTimeout - 补全对象构建的缺失括号:
expiresAt(Instant.now().plusSeconds(60))
2. Mock对象引用不一致
原测试中同时写了两条Mock语句,其中when(ctx.getReportingManagerClient().refreshReportJobLease(...))是冗余的,且如果QueryContext的builder内部对reportingManagerClient做了包装或重新实例化,会导致这条Mock规则绑定到错误的实例上。
修复点:
- 只保留基于
@MockBean实例的Mock语句:when(reportingManagerClient.refreshReportJobLease(anyString())).thenReturn(responseReportJob); - 确认
QueryContext.builder().reportingManagerClient(reportingManagerClient)确实将Mock实例传入了上下文,没有被覆盖。
3. 功能代码的潜在bug
功能代码中存在变量名错误:if (jobLeaseTime != null),但前面定义的变量是jobLeaseTimeout,这会导致条件永远为false,refreshReportJobLease永远不会被调用(如果没修复这个bug,测试可能根本走不到报错的分支)。
修复点:
将条件判断改为:
if (jobLeaseTimeout != null) {
修复后的测试代码示例
@MockBean protected ReportingManagerIntegration reportingManagerClient; @MockBean protected NamedParameterJdbcTemplate jdbcTemplate; @Test public void testCompanionTask() { ReportJob reportJob = generateReportJob(); QueryContext ctx = QueryContext.builder() .jdbc(jdbcTemplate) .busy(new AtomicInteger()) .reportingManagerClient(reportingManagerClient) .job(reportJob) .build(); ReportJob reportJobMock = ReportJob.builder() .createdAt(Instant.now()) .expiresAt(Instant.now().plusSeconds(60)) .build(); Instant leaseRenewTimeout = Instant.now().plusSeconds(300); ResponseEntity<ReportJob> responseReportJob = ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .header(JOB_LEASE_TIMEOUT, leaseRenewTimeout.toString()) .body(reportJobMock); // 仅保留正确的Mock规则 when(reportingManagerClient.refreshReportJobLease(anyString())).thenReturn(responseReportJob); // 构造触发续期的header(确保isJobLeaseExpiring返回true) Instant expiringLeaseTimeout = Instant.now().minusSeconds(10); HttpHeaders headers = new HttpHeaders(); headers.add(JOB_LEASE_TIMEOUT, expiringLeaseTimeout.toString()); CompanionTask task = new CompanionTask(ctx, headers); task.run(); // 验证方法是否被调用 verify(reportingManagerClient, times(1)).refreshReportJobLease(reportJob.getId()); }
内容的提问来源于stack exchange,提问作者Sandy
相关产品推荐
相关产品推荐

