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

Mockito 3.12.4测试Mock失效致空指针及ElasticsearchClient模拟建议

问题解决:MessageFacade.delete方法JUnit测试的NullPointerException及ElasticsearchClient Mock建议

问题背景

在为MessageFacade.java的delete方法编写JUnit 4.12测试用例(基于Mockito 3.12.4)时,测试执行触发NullPointerException,错误发生在schemaMetadata.getDataIndexName()调用处,错误栈如下:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Query Param SCOPE_ID=-41
Query Param ID=c17db8ea-93ee-48b5-9b5d-8d278180d2d9

java.lang.NullPointerException
at org.mars.foo.service.datastore.internal.MessageFacade.delete(MessageFacade.java:242)
at org.mars.foo.service.datastore.internal.MessageFacadeTest.testBulkDelete(MessageFacadeTest.java:74)
...

同时需要获取elasticsearchClient.delete(typeDescriptor, id.toString())方法的Mock实现建议。

核心代码

MessageFacade.java的delete方法

public void delete(FooId scopeId, DataId id)
        throws FooIllegalArgumentException,
        ConfigurationException,
        ClientException {
    ArgumentValidator.notNull(scopeId, SCOPE_ID);
    ArgumentValidator.notNull(id, "id");

    System.out.println("Query Param SCOPE_ID="+scopeId.getId().toString());
    System.out.println("Query Param ID="+id.toString());

    if (!isDatastoreServiceEnabled(scopeId)) {
        LOG.debug("Storage not enabled for account {}, return", scopeId);
        return;
    }

    //ORIGINAL CODE
   DatastoreMessage messageToBeDeleted = find(scopeId, id, DataFetchStyle.FIELDS);
   if (messageToBeDeleted != null) {
       Metadata schemaMetadata = null;
       try {
           schemaMetadata = mediator.getMetadata(scopeId, messageToBeDeleted.getTimestamp().getTime());
       } catch (FooException e) {
           LOG.warn("Retrieving metadata error", e);
       }
       String indexName = schemaMetadata.getDataIndexName();
       TypeDescriptor typeDescriptor = new TypeDescriptor(indexName, MessageSchema.MESSAGE_TYPE_NAME);
       ElasticsearchClient elasticsearchClient = getElasticsearchClient();
       elasticsearchClient.delete(typeDescriptor, id.toString());
   } else {
       LOG.warn("Cannot find the message to be deleted. scopeId: '{}' - id: '{}'", scopeId, id);
   }
}

测试代码

@Test
public void testBulkDelete() throws ClientException, ConfigurationException, FooIllegalArgumentException {
    MessageFacade messageFacade = new MessageFacade(Mockito.mock(ConfigurationProvider.class),Mockito.mock(MessageStoreMediator.class));
    MessageFacade spyMessageFacade = Mockito.spy(messageFacade);

    DatastoreMessage datastoreMessage = new DatastoreMessageImpl();
    datastoreMessage.setTimestamp(new Date());
    Mockito.doReturn(datastoreMessage).when(spyMessageFacade).find(Mockito.any(),Mockito.any(),Mockito.any());

    Metadata schemaMetadata = Mockito.mock(Metadata.class);
    Mockito.doReturn("indexName").when(schemaMetadata).getDataIndexName();

    ElasticsearchClient elasticsearchClient = Mockito.mock(ElasticsearchClient.class);
    Mockito.doNothing().when(elasticsearchClient).delete(Mockito.any(TypeDescriptor.class), Mockito.any(String.class));

    Mockito.doReturn(true).when(spyMessageFacade).isDatastoreServiceEnabled(Mockito.any());

    FooId scopeId = new ScopeId("10");
    DataId id = new DataIdImpl("c17db8ea-93ee-48b5-9b5d-8d278180d2d9");
    spyMessageFacade.delete(scopeId, id);
}

解决方案

1. 解决NullPointerException问题

NPE的根源是:你创建了Metadata的Mock对象,但未让mediator.getMetadata()返回该Mock实例。业务代码中schemaMetadata来自mediator.getMetadata()调用,由于未对该方法Mock,它返回null,最终调用schemaMetadata.getDataIndexName()时触发空指针。

修改测试代码,添加对mediator.getMetadata()的Mock:

// 单独创建mediator的Mock实例,方便后续配置
MessageStoreMediator mediator = Mockito.mock(MessageStoreMediator.class);
MessageFacade messageFacade = new MessageFacade(Mockito.mock(ConfigurationProvider.class), mediator);
MessageFacade spyMessageFacade = Mockito.spy(messageFacade);

// ... 其他测试代码保持不变

// 让mediator.getMetadata()返回你创建的schemaMetadata Mock
Mockito.doReturn(schemaMetadata).when(mediator).getMetadata(Mockito.any(FooId.class), Mockito.any(Long.class));

同时建议在业务代码中添加空值判断,从源头避免类似NPE:

// 在调用schemaMetadata方法前先判空
if (schemaMetadata != null) {
    String indexName = schemaMetadata.getDataIndexName();
    TypeDescriptor typeDescriptor = new TypeDescriptor(indexName, MessageSchema.MESSAGE_TYPE_NAME);
    ElasticsearchClient elasticsearchClient = getElasticsearchClient();
    elasticsearchClient.delete(typeDescriptor, id.toString());
} else {
    LOG.warn("Failed to retrieve metadata, skip delete operation");
}

2. ElasticsearchClient.delete方法的Mock实现建议

当前基础Mock(Mockito.doNothing())已可用,可进一步优化测试覆盖度:

  • 验证方法调用有效性:确保delete方法被正确调用,参数符合预期
// 执行delete操作后添加验证逻辑
Mockito.verify(elasticsearchClient, Mockito.times(1))
    .delete(Mockito.argThat(typeDescriptor -> 
        typeDescriptor.getIndexName().equals("indexName") && 
        typeDescriptor.getTypeName().equals(MessageSchema.MESSAGE_TYPE_NAME)),
    Mockito.eq(id.toString()));
  • Mock异常场景:测试delete方法抛出异常时的业务处理逻辑(若业务代码有异常处理逻辑)
// Mock delete方法抛出ClientException
Mockito.doThrow(new ClientException("Delete failed")).when(elasticsearchClient)
    .delete(Mockito.any(TypeDescriptor.class), Mockito.any(String.class));

// 执行delete后,验证日志或异常处理逻辑(如捕获异常、记录告警等)
  • 绑定ElasticsearchClient Mock实例:若getElasticsearchClient()是MessageFacade的方法,需Mock该方法使其返回你创建的elasticsearchClient实例,否则业务代码会使用真实客户端而非Mock:
Mockito.doReturn(elasticsearchClient).when(spyMessageFacade).getElasticsearchClient();

内容的提问来源于stack exchange,提问作者SSM Tariq

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 17:30:57