Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix InaccessibleObjectException when spying JDK interfaces on JDK 16+
Wrap method.isAccessible = true in try-catch to handle JDK module
system restrictions. This prevents InaccessibleObjectException when
spying on JDK functional interfaces like java.util.function.Consumer.

Co-authored-by: oleksiyp <5249932+oleksiyp@users.noreply.github.com>
  • Loading branch information
Copilot and oleksiyp committed Dec 1, 2025
commit f7b861be5467f3ae97469eae875ddb802f2bfa5f
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ internal class MethodCall(
) : Callable<Any?> {

override fun call(): Any? {
method.isAccessible = true
try {
method.isAccessible = true
} catch (ignored: Throwable) {
// Skip setting accessible - method may be in a JDK module that doesn't open to unnamed modules (JDK 16+)
}
return method.invoke(self, *args)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.mockk.test

import io.mockk.every
import io.mockk.spyk
import org.junit.jupiter.api.Test
import java.util.function.Consumer
import kotlin.test.assertEquals

/**
* Test for issue: java.base does not "opens java.util.function" since 1.14.0
*
* This test validates that spying on JDK functional interfaces (like Consumer)
* works correctly on JDK 16+ without requiring --add-opens JVM arguments.
*
* The problem occurs when MockK tries to make internal JDK lambda methods accessible
* (via `method.isAccessible = true`) which is not permitted on JDK 16+ due to
* the Java module system's strong encapsulation.
*/
class JdkConsumerSpyTest {

@Test
fun `spying on Consumer should work without InaccessibleObjectException`() {
val results = mutableListOf<String>()

val consumer = spyk<Consumer<String>> {
every { this@spyk.accept(any()) } answers { results.add(firstArg<String>()) }
}

consumer.accept("test1")
consumer.accept("test2")

assertEquals(listOf("test1", "test2"), results)
}

@Test
fun `spying on Consumer and using andThen should work`() {
val results = mutableListOf<String>()

val consumer1: Consumer<String> = spyk<Consumer<String>> {
every { this@spyk.accept(any()) } answers { results.add("first: ${firstArg<String>()}") }
}

val consumer2: Consumer<String> = Consumer { results.add("second: $it") }

// This is the pattern that triggered the original issue
// Consumer.andThen creates an internal lambda that MockK may try to access
val combined = consumer1.andThen(consumer2)

combined.accept("value")

assertEquals(listOf("first: value", "second: value"), results)
}
}