You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

泛型类使用spyk触发StackOverflowError,需保留父类调用能否解决?

StackOverflowError When Using spyk on Generic Class with Super Method Call in MockK

Yes, you can absolutely use spyk for this scenario—this stack overflow issue is a specific edge case with how MockK handles method interception for generic parent classes when you explicitly call super. Let’s walk through why it happens and how to fix it.

Why the StackOverflowError Happens

When you create a spyk of your ChildClazz, MockK generates a proxy instance that wraps your real class. Normally, this proxy delegates calls to the real object, but when your overridden foobar method calls super.foobar(view), the proxy incorrectly routes this superclass method call back to the child’s intercepted method instead of the parent class’s actual implementation. This creates an infinite loop that eventually triggers the StackOverflowError.

Fixes to Use spyk Successfully

Option 1: Explicitly Call the Real Method with callOriginal()

By defining the behavior of the foobar method to invoke the real implementation directly, you break the loop and ensure the superclass call works as expected:

@Test
fun callFoobar() {
    val view = mockk<SomeView>()
    val childClazz = spyk(ChildClazz(), recordPrivateCalls = true)
    
    // Explicitly tell MockK to use the real method implementation
    every { childClazz.foobar(any()) } answers { callOriginal() }
    
    childClazz.foobar(view)
    assertk.assert(childClazz.called).isEqualTo(true)
}

This works because callOriginal() bypasses the proxy’s interception loop and executes the actual code in your ChildClazz, including the super.foobar(view) call and setting called = true.

Option 2: Use mockkClass with Relaxed Settings

If you prefer a cleaner setup, you can use mockkClass instead of spyk (it still supports private method recording) with relaxed settings to automatically delegate to real methods unless specified otherwise:

@Test
fun callFoobar() {
    val view = mockk<SomeView>()
    val childClazz = mockkClass(ChildClazz::class, relaxed = true, recordPrivateCalls = true)
    
    childClazz.foobar(view)
    assertk.assert(childClazz.called).isEqualTo(true)
}

Option 3: Directly Invoke the Superclass Method (Advanced)

If you need more control over the superclass call (e.g., to avoid invoking the entire child method), you can use reflection to call the parent class’s method directly from the test:

@Test
fun callFoobar() {
    val view = mockk<SomeView>()
    val childClazz = spyk(ChildClazz(), recordPrivateCalls = true)
    
    every { childClazz.foobar(any()) } answers {
        // Use reflection to call the parent class's foobar method
        val superFoobar = ParentClazz::class.java.getDeclaredMethod(
            "foobar",
            ParentView::class.java
        )
        superFoobar.invoke(this@spyk, firstArg())
        // Execute the child's custom logic
        this@spyk.called = true
    }
    
    childClazz.foobar(view)
    assertk.assert(childClazz.called).isEqualTo(true)
}

Handling Private Methods

All these options preserve recordPrivateCalls = true, so you can still verify or stub private methods using MockK’s private API support (e.g., verifyPrivate(childClazz) { call("privateMethod") }).

内容的提问来源于stack exchange,提问作者Denis Vodolazskiy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:05:49