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

Mockito Stubbed Spy调用异常:如何在@Test中混合调用真实方法与存根内部方法?

Fixing Stubbed Spy Inconsistencies in Kotlin Mockito Tests

I’ve run into this exact issue before with Kotlin and Mockito—those finicky spies that sometimes hit real methods and sometimes don’t, plus weird debug errors. Let’s break down the root causes and fix this step by step.

First, Understand the Core Kotlin + Mockito Quirks

Kotlin classes and methods are final by default, which plays havoc with Mockito’s ability to create spies (since spies rely on subclassing to override behavior). This is the #1 reason for inconsistent spy behavior.

Step 1: Add Mockito Inline Dependency

To get Mockito working with Kotlin’s final classes/methods, you need the mockito-inline dependency instead of just mockito-core. It enables mocking of final types without having to mark everything as open.

For Gradle (Kotlin DSL):

testImplementation("org.mockito:mockito-inline:5.6.0") // Use latest version

For Maven:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <version>5.6.0</version>
    <scope>test</scope>
</dependency>

Step 2: Correctly Create and Stub Your Spy

The biggest mistake people make is using when(spy.method()).thenReturn(...) with spies—this triggers the real method call before stubbing it, leading to unexpected behavior. Instead, use doReturn(...).when(spy).method() to skip the real call entirely.

Example Workflow

Say you have a Kotlin class where you want to call the real process() method, but stub its internal fetchConfig() call:

class DataProcessor {
    fun process(input: String): String {
        val config = fetchConfig() // Stub this
        return "$input processed with $config"
    }

    fun fetchConfig(): String {
        return "default-config" // Real implementation we want to bypass
    }
}

Here’s the correct test setup:

import org.junit.jupiter.api.Test
import org.mockito.Mockito
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.verify

class DataProcessorTest {

    @Test
    fun `process uses stubbed config but real processing logic`() {
        // 1. Create a spy from a REAL instance of your class
        val processorSpy = Mockito.spy(DataProcessor())

        // 2. Stub the internal method WITHOUT triggering the real call
        doReturn("stubbed-config").`when`(processorSpy).fetchConfig()

        // 3. Call the real method on the spy
        val result = processorSpy.process("test-input")

        // 4. Verify behavior
        assert(result == "test-input processed with stubbed-config")
        verify(processorSpy).fetchConfig() // Confirm stubbed method was called
    }
}

Step 3: Fix Debugging Errors with Spies

If you’re getting errors when debugging spy methods, check these:

  • Ensure your spy is initialized properly: Don’t use @Spy without explicitly initializing the real instance first (especially if your class has constructor dependencies). For example:
    @Spy
    lateinit var processorSpy: DataProcessor
    
    @BeforeEach
    fun setUp() {
        processorSpy = Mockito.spy(DataProcessor()) // Explicitly create spy from real instance
    }
    
  • Avoid mocking final methods: Even with mockito-inline, some edge cases with final methods can cause issues. If you’re stuck, mark the method you want to stub as open in your Kotlin class.
  • Check for constructor side effects: If your class’s constructor runs logic that relies on external dependencies, make sure those dependencies are mocked before creating the spy.

Key Takeaways

  • Use mockito-inline to handle Kotlin’s default final types.
  • Always use doReturn(...).when(spy).method() instead of when(spy.method()).thenReturn(...) for spies to avoid accidental real method calls.
  • Initialize spies from real class instances, not empty mocks.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:04:38