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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 18:34:58