如何在Mock时严格校验List泛型类型,解决Mockito.anyList()泛型匹配失效问题
问题根因
Java泛型采用类型擦除机制实现,编译后List<A>和List<B>的泛型参数信息会被抹除,运行时二者都属于List类型,因此Mockito的anyList()泛型写法不会实际校验列表存储的元素类型,两个匹配规则会被判定为重复的通用列表匹配规则,最终默认执行后定义的规则,导致返回结果不符合预期。
解决方案
方案1:直接匹配列表实例(最简单,优先推荐)
如果测试用例中listA和listB是你自行构造的测试对象,直接匹配列表实例即可,无需判断泛型:
// 测试用例中提前构造测试用的listA和listB List<A> listA = new ArrayList<>(); List<B> listB = new ArrayList<>(); // Mock逻辑直接匹配具体实例 when(customMapper.map(eq(listA), eq(C.class))).thenReturn(Collections.emptyList()); when(customMapper.map(eq(listB), eq(C.class))).thenReturn(Collections.singletonList(new C()));
方案2:自定义参数匹配器校验元素类型
如果无法直接匹配列表实例(比如列表是业务代码内部生成的),可以自定义ArgumentMatcher校验列表内的元素类型来区分:
// 匹配元素类型为A的列表 ArgumentMatcher<List<?>> isListOfA = list -> list.stream().allMatch(item -> item instanceof A); // 匹配元素类型为B的列表 ArgumentMatcher<List<?>> isListOfB = list -> list.stream().allMatch(item -> item instanceof B); // 改写Mock逻辑 when(customMapper.map(argThat(isListOfA), eq(C.class))).thenReturn(Collections.emptyList()); when(customMapper.map(argThat(isListOfB), eq(C.class))).thenReturn(Collections.singletonList(new C()));
注意:如果存在空列表场景,可根据业务逻辑补充空列表的判断规则,比如指定空列表判定为
List<A>类型。
方案3:调整Mapper方法签名(可选,长期最优)
如果允许修改customMapper.map的方法定义,可以新增参数接收列表元素的类型,从根源避免泛型擦除问题:
// 调整后的方法签名,新增sourceType参数明确源元素类型 <T, R> List<R> map(List<T> source, Class<T> sourceType, Class<R> targetType);
调整后Mock逻辑可以直接匹配源类型参数:
when(customMapper.map(anyList(), eq(A.class), eq(C.class))).thenReturn(Collections.emptyList()); when(customMapper.map(anyList(), eq(B.class), eq(C.class))).thenReturn(Collections.singletonList(new C()));
方案4:按调用顺序返回(临时应急,不推荐长期使用)
如果能确定两次map方法的调用顺序固定,可以通过多返回值特性按顺序返回结果:
when(customMapper.map(anyList(), eq(C.class))) .thenReturn(Collections.emptyList()) // 第一次调用对应listA转换,返回空列表 .thenReturn(Collections.singletonList(new C())); // 第二次调用对应listB转换,返回单元素列表
注意:该方案强依赖调用顺序,后续业务代码调整调用顺序会直接导致Mock失效,仅可作为临时应急方案使用。
内容的提问来源于stack exchange,提问作者Sriharsha g.r.v
相关产品推荐
相关产品推荐

