Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support mocking non-accessible parent methods #785

Merged
merged 1 commit into from Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion dsl/jvm/src/main/kotlin/io/mockk/InternalPlatformDsl.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -134,6 +136,11 @@ actual object InternalPlatformDsl {
}
}

private fun KClass<*>.allAncestorFunctions(): Sequence<KFunction<*>> {
return (sequenceOf(this) + this.allSuperclasses.asSequence())
.flatMap { it.functions }
}

private fun List<KType>.anyIsInstance(value: Any?): Boolean {
return any { bound ->
val classifier = bound.classifier
Expand Down
66 changes: 66 additions & 0 deletions 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<ChildWithShadowedMethod> {
every { call() } answers { callOriginal() }
every { this@mockk["callPrivate"]() } returns "Mock"
}

assertEquals(mock.call(), "Mock")
}

@Test
fun testPrivateCallMock() {
val mock = mockk<Child> {
every { call() } answers { callOriginal() }
every { this@mockk["callPrivate"]() } returns "Mock"
}

assertEquals(mock.call(), "Mock")
}

@Test
fun testPrivateCallMockForGrandChild() {
val mock = mockk<GrandChild> {
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"]() }
}
}