MockK无法解析抽象类方法中的泛型T问题求助
Kotlin泛型抽象类Mock时的泛型解析错误解决
场景还原
所有业务服务都继承自泛型抽象类DefaultBaseService<T :HasID<ID>, ID>,该类包含以下基础CRUD方法:
override suspend fun update(id: ID, obj: T): T { obj.id = id return save(obj) } override suspend fun update(id: ID, map: HashMap<String, Any>): T { /* ... */ return update(id, obj) }
服务继承示例:
@Service class FooService(val fooRepository: FooRepository): DefaultBaseService<Foo, ObjectId>(fooRepository, Foo::class)
现在要对注入了该服务的RabbitMQ监听器FooListener做MockK+JUnit测试,测试代码如下:
@ExtendWith(MockKExtension::class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @SpringBootTest class FooListenerTests { @MockK lateinit var fooService: FooService @MockK lateinit var barService: BarService @InjectMockKs lateinit var listener: FooListener @BeforeAll fun setUp() = MockKAnnotations.init(this, relaxUnitFun = true) @Test fun `should listen do event`() { coEvery { fooService.create(any()) } returns Templates.foo coEvery { fooService.save(any()) } returns Templates.foo coEvery { fooService.update(any(), any<Foo>()) } coEvery { barService.create(any()) } returns Templates.bar listener.listenToSomething(Templates.event) } }
运行测试时抛出错误:
Jun 27, 2023 12:16:14 PM io.mockk.impl.log.JULLogger warn WARNING: Failed to transform class com/numih/mongodb/commons/service/DefaultBaseService java.lang.IllegalArgumentException: Cannot resolve T from static java.lang.Object com.numih.mongodb.commons.service.DefaultBaseService.update$suspendImpl(?)
已经尝试过改用Mockito、把any<Foo>()替换为ofType(Foo::class),问题依旧,需要解决这个泛型解析问题。
可行解决办法
1. 改用Mock接口而非具体服务类
MockK和Mockito在处理泛型抽象类的子类时,容易因为父类泛型参数的字节码信息丢失导致解析失败。可以给服务类定义对应的接口,让服务类实现接口+继承抽象类,测试时Mock接口:
首先定义接口:
interface FooServiceOps : DefaultBaseService<Foo, ObjectId> { // 如有额外业务方法,在这里声明 }
修改服务类实现接口:
@Service class FooService(val fooRepository: FooRepository): FooServiceOps, DefaultBaseService<Foo, ObjectId>(fooRepository, Foo::class)
测试类中Mock接口:
@MockK(relaxed = true) lateinit var fooService: FooServiceOps @MockK(relaxed = true) lateinit var barService: BarServiceOps // 同理给BarService也加接口 @InjectMockKs lateinit var listener: FooListener
2. 手动注入依赖,避免@InjectMockKs的泛型冲突
@InjectMockKs在处理泛型类注入时可能出现隐式的泛型解析问题,改为手动创建监听器实例,直接传入Mock对象:
修改测试类的setUp和变量声明:
@ExtendWith(MockKExtension::class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) class FooListenerTests { // 可以去掉@SpringBootTest,除非需要Spring上下文 private lateinit var fooService: FooService private lateinit var barService: BarService private lateinit var listener: FooListener @BeforeAll fun setUp() { fooService = mockk(relaxed = true) barService = mockk(relaxed = true) listener = FooListener(fooService, barService) // 手动构造实例 } @Test fun `should listen to event`() { coEvery { fooService.create(any()) } returns Templates.foo coEvery { fooService.save(any()) } returns Templates.foo coEvery { fooService.update(any(), any<Foo>()) } returns Templates.foo // 注意要指定返回值,之前的代码没写返回值 coEvery { barService.create(any()) } returns Templates.bar listener.listenToSomething(Templates.event) } }
3. 调整Mock的relax模式
把relaxUnitFun = true换成relaxed = true,后者会自动放松所有方法(包括泛型方法),能减少泛型解析时的冲突:
@BeforeAll fun setUp() = MockKAnnotations.init(this, relaxed = true) // 或者在@MockK注解里指定: @MockK(relaxed = true) lateinit var fooService: FooService
4. 确保抽象类泛型参数的具体化
检查DefaultBaseService的构造方法是否正确传递了泛型类型信息,比如你已经传入了Foo::class,这部分没问题,但可以尝试在抽象类中给泛型方法添加@JvmName或显式声明泛型参数,帮助字节码工具识别:
比如修改抽象类的update方法:
@JvmName("updateByIdAndObj") override suspend fun update(id: ID, obj: T): T { obj.id = id return save(obj) }
这样在Mock时可以通过方法名更精准地匹配,避免泛型混淆。
内容的提问来源于stack exchange,提问作者Alice
相关产品推荐
相关产品推荐

