Mockito具体化mock方法将mock识别为超类而非泛型类型的问题
问题背景
我们需要封装一个简化工具方法,用来Mock第三方库的泛型类ClassIWantMockedConstructor<T>(该类包含返回泛型类型T的whatever()方法)。初始实现如下:
public interface Execution<T extends ParentClass> { void execute(T mock) throws Exception; } public static <T extends ParentClass> void withMockedConstruction(Execution<T> execution) { T mock = mock(); try (@SuppressWarnings("unused") MockedConstruction<?> construction = mockConstruction( ClassIWantMockedConstructor.class, (mocked, ctx) -> when(mocked.whatever()).thenReturn(mock) )) { execution.execute(mock); } catch (Throwable t) { throw new RuntimeException("Unexpected error during tests, please check the tests", t); } }
但传入ParentClass的子类ChildClass的Execution Lambda时,泛型参数T会被识别为ParentClass,导致生成的Mock对象是ParentClass类型,调用execution.execute(mock)时抛出ClassCastException:
// 调用方式 withMockedConstruction((ChildClass mockService) -> { ... }); // 调试时的问题 public static <T extends ParentClass> void withMockedConstruction(Execution<T> execution) { T mock = mock(); // mock是ParentClass的Mock,T被推断为ParentClass ... execution.execute(mock); // 抛出ClassCastException }
手动传入Class<T>参数修改方法签名后可正常工作:
// 修改后的方法签名 public static <T extends ParentClass> void withMockedConstruction(Class<T> clazz, Execution<T> execution) { T mock = mock(clazz); // mock正常是ChildClass类型 ... } // 正常调用方式 withMockedConstruction(ChildClass.class, mockService -> { ... });
问题解答
1. 能否仅通过具体化(reified)实现该效果?
Java本身不支持reified泛型——泛型类型信息在编译后会被擦除,运行时无法直接获取Execution<T>中T的实际类型。虽然Kotlin的reified关键字可以保留泛型类型,但纯Java环境下无法做到这一点。
Mockito的mock()方法如果不传Class<T>参数,只能通过类型推断生成Mock,但此处的类型推断会因Lambda的目标类型模糊而退化为ParentClass,所以仅靠Java自身机制无法通过“具体化”实现预期效果。
2. 是否可强制检查lambda的具体化类型?
无法直接强制检查Lambda的具体化类型。Lambda表达式在编译时会被转换为匿名内部类,泛型擦除后,运行时无法获取Lambda所期望的T的实际类型。
虽然可以通过反射尝试解析Lambda字节码推断类型,但这种方式复杂且不可靠,易受编译优化、Lambda实现细节影响,不适合在测试工具中使用。
3. 传入Class<T>是否为当前场景的最优方案?
是的,传入Class<T>是纯Java环境下的最优方案:
- 简单直接:明确指定类型,避免类型推断的模糊性,Mockito可精准生成对应类型的Mock对象。
- 可读性好:调用方一眼就能看出当前Mock的目标类型,代码意图清晰。
- 兼容性强:适配所有Java版本,无需依赖额外语言特性或第三方库。
另外,也可考虑通过重载方法结合Supplier优化调用体验,但本质仍需显式指定类型,示例如下:
public static <T extends ParentClass> void withMockedConstruction(Supplier<T> mockSupplier, Execution<T> execution) { T mock = mockSupplier.get(); // 后续逻辑不变 } // 调用方式 withMockedConstruction(() -> mock(ChildClass.class), mockService -> { ... });
但这种方式并未比直接传入Class<T>更简洁,反而增加了调用复杂度,因此还是传入Class<T>的方案更优。
内容的提问来源于stack exchange,提问作者Gabriel Andrade

