Mockito Stubbed Spy调用异常:如何在@Test中混合调用真实方法与存根内部方法?
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
@Spywithout 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 asopenin 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-inlineto handle Kotlin’s default final types. - Always use
doReturn(...).when(spy).method()instead ofwhen(spy.method()).thenReturn(...)for spies to avoid accidental real method calls. - Initialize spies from real class instances, not empty mocks.
内容的提问来源于stack exchange,提问作者ArtiomLK

