1. 概述

在使用 Mockito 编写 Java 单元测试时,我们经常需要处理接受泛型 List 参数的方法(如 List<String>List<Integer> 等)。但由于 Java 的类型擦除机制,处理这类情况需要额外注意。

本文将深入探讨如何在 Mockito 中正确使用泛型 List 匹配器,涵盖 Java 7 和 Java 8+ 两种场景的解决方案。

2. 问题引入

通过一个示例来理解泛型与 Mockito 的冲突点。假设我们有一个带默认方法的接口:

interface MyInterface {
    default String extractFirstLetters(List<String> words) {
        return String.join(", ", words.stream().map(str -> str.substring(0, 1)).toList());
    }
}

extractFirstLetters() 方法接收 List<String> 并返回单词首字母拼接的字符串。现在尝试在测试中模拟该方法:

MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(any(List.class))).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<String>()));

虽然测试能通过,但编译器会抛出警告:

Unchecked assignment: 'java.util.List' to 'java.util.List<java.lang.String>' 

⚠️ 问题根源:使用 any(List.class) 匹配泛型参数时,编译器无法在编译期验证原始 List 是否只包含 String 元素。接下来我们分场景解决。

3. Java 7 泛型 List 匹配方案

在遗留 Java 7 项目中,类型推断能力有限。使用 ArgumentMatchers 时必须显式指定泛型类型,否则会导致编译错误。

正确写法示例:

// Java 7
MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(ArgumentMatchers.<String>anyList())).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<>()));

✅ 关键点:通过 <String> 显式声明 anyList() 的泛型类型。若省略此声明,anyList() 返回 List<?>,与 List<String> 不匹配,直接导致编译失败。

4. Java 8+ 泛型 List 匹配方案

Java 8+ 的编译器具备更强的类型推断能力,无需显式指定泛型参数:

MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(anyList())).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<>()));

✅ 优势:编译器自动从 extractFirstLetters(List<String>) 的方法签名推断出泛型类型,无警告且代码更简洁。

常见踩坑:any() 的误用

部分开发者可能尝试用 any() 匹配器:

MyInterface mock = Mockito.mock(MyInterface.class);
when(mock.extractFirstLetters(any())).thenReturn("a, b, c, d, e");
assertEquals("a, b, c, d, e", mock.extractFirstLetters(new ArrayList<>()));

虽然 Java 8+ 能正确推断类型,但存在两个问题:

  1. any() 匹配所有对象类型,缺乏类型安全性
  2. 代码可读性降低,无法明确表达"需要 List 类型参数"的意图

❌ 最佳实践:对于 List<String> 参数,优先使用 anyList() 而非 any(),确保:

  • 类型安全性
  • 代码自解释性
  • 精确匹配语义

5. 总结

Mockito 的 List 匹配器在处理泛型参数时需注意类型擦除和版本差异:

Java 版本 推荐方案 示例
Java 7 显式声明泛型 ArgumentMatchers.<String>anyList()
Java 8+ 自动类型推断 anyList()

掌握这些技巧能帮助我们在遗留项目和现代 Java 代码中编写更健壮、更易维护的单元测试。记住:类型安全永远比少写几个字符更重要


原始标题:List Matchers with Generics in Mockito | Baeldung