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

Mockito verify报错:传入参数超出方法允许范围问题排查

Mock类方法输入参数验证问题

我在验证Mock类方法的输入参数时遇到问题,结果显示传入的输入值超出了方法允许的范围。以下是相关代码及问题说明:

测试代码

class MyClass {

    // 项目本地类
    @Mock
    private ElasticSearchService elasticSearchService;

    private final static String INDEX_NAME = "index-test";
    private Consumer<Message<List<Command<Entity>>>> index;

    @BeforeEach
    void beforeEach() {
        index = new EntityIndexFactory(elasticSearchService, new ObjectMapper()).create();
    }

    @Test
    void happyCase() {
        Event event = new Event();
        event.setId("eventId");
        event.setLastUpdate(1L);
        Command<Entity> command = Command.builder()
                .id(event.getId())
                .type("event")
                .version(event.getLastUpdate())
                .actions(Map.of(INDEX_NAME, Action.UPSERT))
                .source(event)
                .build();
        when(elasticSearchService.indexExists(any())).thenReturn(false);
        index.accept(MessageBuilder.createMessage(List.of(command), new MessageHeaders(null)));
        List<DocWriteRequest<?>> processDocs = new ArrayList<>();
        processDocs.add(FactoryTestUtils.upsertRequest(INDEX_NAME, command));
        verify(elasticSearchService).process(processDocs);
    }
}

待测试类(EntityIndexFactory)

@Service
@RequiredArgsConstructor
public class EntityIndexFactory {

    @NonNull
    private ElasticSearchService elasticSearchService;

    @NonNull
    private ObjectMapper objectMapper;

    public <E extends Entity> Consumer<Message<List<Command<E>>>> create() {
        return message -> {
            try {
                List<DocWriteRequest<?>> mappedCommands = message.getPayload().stream()
                        .map(this::mapCommand)
                        .flatMap(List::stream)
                        .toList();
                elasticSearchService.process(mapSecondary(mappedCommands));
            } catch (Exception e) {
                throw new PersistenceException(e);
            }
        };
    }

Mock类(ElasticSearchService)

@Service
@RequiredArgsConstructor
public class ElasticSearchService {

    @NonNull
    private RestHighLevelClient esClient;

    public boolean process(List<DocWriteRequest<?>> requests) {
        BulkRequest bulkRequest = new BulkRequest();
        try {
            requests.forEach(bulkRequest::add);
            BulkResponse bulkResponse = esClient.bulk(bulkRequest, RequestOptions.DEFAULT);

            boolean responseSuccess = !bulkResponse.hasFailures();

            if (!responseSuccess) {
                log.info("Failed to execute index operation : {}", bulkResponse.buildFailureMessage());
                handleRetryableStatuses(bulkResponse);
            }

            return responseSuccess;

        } catch (IOException ex) {
            throw new RecoverableException("Exception caused during bulk execution", INFO_CODE, ex);
        }
    }

错误情况

调用verify()时出现参数不匹配错误,错误信息中包含了indexExists()方法的调用记录——该方法是ElasticSearchService中的另一个方法,会在EntityIndexFactory的mapSecondary()执行期间被调用。

我原本预期断言仅会对比process()方法的输入参数(类型为List<DocWriteRequest<?>>),但错误日志里却出现了另一个方法的调用信息。

补充:mapSecondary()方法代码

private List<DocWriteRequest<?>> mapSecondary(List<DocWriteRequest<?>> esRequests) {
    Map<String, Boolean> secondaryIndexExistence = new HashMap<>();
    List<DocWriteRequest<?>> extendedList = new ArrayList<>(esRequests);
    for (DocWriteRequest<?> request : esRequests) {
        String index = request.index();
        if (!secondaryIndexExistence.containsKey(index)) {
            secondaryIndexExistence.put(index, elasticSearchService.indexExists(index + secondaryIndicesSuffix));
        }
        if (Boolean.TRUE.equals(secondaryIndexExistence.get(index))) {
            if (request instanceof IndexRequest indexRequest) {
                extendedList.add(secIndexUpsertRequest(indexRequest));
            } else extendedList.add(secIndexDeleteRequest((DeleteRequest) request));
        }
    }
    return extendedList;

}

问题原因及解决方案

  1. 参数不匹配核心原因
    你手动构造的processDocs列表和实际传入process()的列表并非同一实例:mapSecondary()中返回的extendedList是基于原列表创建的新ArrayList对象,即使内容一致,Mockito默认通过对象引用相等性验证参数,因此会判定不匹配。

  2. 错误日志包含indexExists()的原因
    Mockito的参数不匹配错误会展示所有Mock对象的交互记录,帮助排查预期外的调用。indexExists()确实在mapSecondary()中被调用了,所以会出现在日志里,并非异常混入。

  3. 修复方案

方案一:使用参数匹配器验证列表内容

如果只需要验证列表元素符合预期,而非严格匹配对象实例,用ArgumentMatchers自定义匹配逻辑:

verify(elasticSearchService).process(argThat(list -> {
    if (list.size() != 1) {
        return false;
    }
    DocWriteRequest<?> request = list.get(0);
    // 验证索引名称、请求类型等关键属性
    return INDEX_NAME.equals(request.index()) && request instanceof IndexRequest;
}));

方案二:保证预期请求与实际请求内容一致

要么让FactoryTestUtils.upsertRequest()生成的DocWriteRequest重写equals()和hashCode()方法,要么直接调用待测试类的方法生成预期请求:

// 在测试中调用mapCommand生成预期请求
Command<Entity> command = ...; // 原代码中的command
EntityIndexFactory factory = new EntityIndexFactory(elasticSearchService, new ObjectMapper());
List<DocWriteRequest<?>> expectedRequests = factory.mapCommand(command);
// 因indexExists返回false,mapSecondary不会添加新元素,直接用该列表验证
verify(elasticSearchService).process(expectedRequests);
  1. 额外注意事项
    确保indexExists()的Stub逻辑准确:如果secondaryIndicesSuffix非空,用eq(INDEX_NAME + secondaryIndicesSuffix)替代any(),可以避免意外的匹配问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 23:25:58