diff --git a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt index b4d184ff9..6854e59d9 100644 --- a/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt +++ b/dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt @@ -7,10 +7,12 @@ import java.lang.reflect.Method import java.util.concurrent.atomic.AtomicLong import kotlin.coroutines.Continuation import kotlin.reflect.KClass +import kotlin.reflect.KFunction import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 import kotlin.reflect.KType import kotlin.reflect.KTypeParameter +import kotlin.reflect.full.allSuperclasses import kotlin.reflect.full.functions import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.isAccessible @@ -100,7 +102,7 @@ actual object InternalPlatformDsl { anyContinuationGen: () -> Continuation<*> ): Any? { val params = arrayOf(self, *args) - val func = self::class.functions.firstOrNull { + val func = self::class.allAncestorFunctions().firstOrNull { if (it.name != methodName) { return@firstOrNull false } @@ -134,6 +136,11 @@ actual object InternalPlatformDsl { } } + private fun KClass<*>.allAncestorFunctions(): Sequence> { + return (sequenceOf(this) + this.allSuperclasses.asSequence()) + .flatMap { it.functions } + } + private fun List.anyIsInstance(value: Any?): Boolean { return any { bound -> val classifier = bound.classifier diff --git a/mockk/jvm/src/test/kotlin/io/mockk/it/PrivateParentMethodTest.kt b/mockk/jvm/src/test/kotlin/io/mockk/it/PrivateParentMethodTest.kt new file mode 100644 index 000000000..5798337ad --- /dev/null +++ b/mockk/jvm/src/test/kotlin/io/mockk/it/PrivateParentMethodTest.kt @@ -0,0 +1,66 @@ +package io.mockk.it + +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * Test mocking non-visible parent methods through dynamic calls. Issue #425 + */ +class PrivateParentMethodTest { + open class Parent { + open fun call(): String = callPrivate() + private fun callPrivate() = "Real" + } + + open class Child: Parent() + + class ChildWithShadowedMethod: Parent() { + override fun call(): String = callPrivate() + fun callPrivate() = "Shadowed" + } + + class GrandChild: Child() + + @Test + fun testChildAlwaysMockedFirst() { + val mock = mockk { + every { call() } answers { callOriginal() } + every { this@mockk["callPrivate"]() } returns "Mock" + } + + assertEquals(mock.call(), "Mock") + } + + @Test + fun testPrivateCallMock() { + val mock = mockk { + every { call() } answers { callOriginal() } + every { this@mockk["callPrivate"]() } returns "Mock" + } + + assertEquals(mock.call(), "Mock") + } + + @Test + fun testPrivateCallMockForGrandChild() { + val mock = mockk { + every { call() } answers { callOriginal() } + every { this@mockk["callPrivate"]() } returns "Mock" + } + + assertEquals(mock.call(), "Mock") + } + + @Test + fun testPrivateCallVerify() { + val mock = spyk(Child(), recordPrivateCalls = true) + + mock.call() + + verify { mock["callPrivate"]() } + } +}