Skip to content

Commit

Permalink
Support mocking non-accessible parent methods
Browse files Browse the repository at this point in the history
- Add support for mocking non-accessible parent methods using Mockk
- The class being mocked is always searched first for the specified
  method, then all superclasses are checked in no specific order. If
  multiple superclasses have matching signatures for the method then
  there is no guarantee which will be mocked first.
- Fixes mockk#425
  • Loading branch information
npars committed Feb 2, 2022
1 parent c9c842b commit 9741290
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
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"]() }
}
}

0 comments on commit 9741290

Please sign in to comment.