Mockito如何区分两个不同泛型类型方法的when()模拟?
解决Mockito因泛型类型擦除无法区分Generic<T,R>方法调用的问题
这个问题确实是Java泛型类型擦除带来的经典Mockito痛点——编译时的泛型信息到运行时就被擦除了,Mockito默认只能识别到Generic这个原始类型,所以会把两个调用当成同一个来处理。下面给你几个实用的解决方案,你可以根据自己的场景选择:
1. 自定义ArgumentMatcher(最常用)
你可以写一个自定义的参数匹配器,专门用来区分不同泛型参数的Generic对象。核心思路是:要么利用Generic类本身提供的类型信息(比如保存了泛型对应的Class对象),要么通过反射获取泛型参数(仅限特定场景)。
示例代码:
首先定义匹配器:
import org.mockito.ArgumentMatcher; class GenericTypeMatcher<T, R> implements ArgumentMatcher<Generic<T, R>> { private final Class<T> expectedTypeT; private final Class<R> expectedTypeR; public GenericTypeMatcher(Class<T> expectedTypeT, Class<R> expectedTypeR) { this.expectedTypeT = expectedTypeT; this.expectedTypeR = expectedTypeR; } @Override public boolean matches(Object argument) { if (!(argument instanceof Generic)) { return false; } Generic<?, ?> generic = (Generic<?, ?>) argument; // 情况1:如果Generic类本身保存了泛型的Class对象(推荐提前改造Generic类) return expectedTypeT.equals(generic.getTClass()) && expectedTypeR.equals(generic.getRClass()); // 情况2:如果无法修改Generic类,仅当参数是匿名内部类实例时可用(反射获取泛型) // Type superType = argument.getClass().getGenericSuperclass(); // if (superType instanceof ParameterizedType) { // ParameterizedType paramType = (ParameterizedType) superType; // Type[] typeArgs = paramType.getActualTypeArguments(); // return typeArgs[0].equals(expectedTypeT) && typeArgs[1].equals(expectedTypeR); // } // return false; } }
然后在测试中使用:
import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.when; // ... when(someObject.someMethod(argThat(new GenericTypeMatcher<>(TypeA.class, TypeB.class)))) .thenReturn(responseOne()); when(someObject.someMethod(argThat(new GenericTypeMatcher<>(TypeB.class, TypeC.class)))) .thenReturn(responseTwo());
2. 使用Mockito的Answer动态判断
如果不想写单独的匹配器,你可以用Answer接口在运行时动态检查传入的参数,再返回对应的响应:
import static org.mockito.Mockito.when; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; // ... when(someObject.someMethod(any(Generic.class))).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Generic<?, ?> inputGeneric = invocation.getArgument(0); // 同样需要根据Generic的类型信息判断 if (inputGeneric.getTClass().equals(TypeA.class) && inputGeneric.getRClass().equals(TypeB.class)) { return responseOne(); } else if (inputGeneric.getTClass().equals(TypeB.class) && inputGeneric.getRClass().equals(TypeC.class)) { return responseTwo(); } // 处理意外情况 throw new IllegalArgumentException("Unexpected Generic type"); } });
3. 提前改造Generic类(最优解)
如果你的项目允许修改Generic类,最稳妥的方式是在类中保存泛型对应的Class对象,这样不管运行时怎么擦除,都能直接拿到类型信息:
public class Generic<T, R> { private final Class<T> tClass; private final Class<R> rClass; // 构造函数传入Class对象 public Generic(Class<T> tClass, Class<R> rClass) { this.tClass = tClass; this.rClass = rClass; } // 提供getter方法 public Class<T> getTClass() { return tClass; } public Class<R> getRClass() { return rClass; } // 其他原有逻辑... }
这样上面的匹配器和Answer都能稳定工作,不用依赖反射的特殊场景。
内容的提问来源于stack exchange,提问作者RichardK
相关产品推荐
相关产品推荐

