0

I'm having a hard time trying to get a private method in Kotlin using reflection in order to pass it as a parameter to a higher order function, here is what I got and what I need to do:

The function that gets the private method, probably what I should change or fix:

inline fun <reified T> T.getPrivateFunc(name: String): KFunction<*> {
    return T::class.declaredMemberFunctions.first { 
        it.name == name 
    }.apply { 
        isAccessible = true 
    }
}

This is the high order function I have:

class MyService {

    fun myHigherOrderFunction(action: () -> Unit) { /*...*/ }
}

These are the class and the private method I need to get somehow:

class SystemUnderTest {

    fun privateFunc() { /*...*/ }
}

Finally a unit test where I I'm trying to make sure the proper method is passed to the high order function, I omitted details for simplification:

// ...
val serviceMock = MyService()
val sut = SystemUnderTest()
// Here is what I'm trying to accomplish
val privateMethod = sut.getPrivateMethod("privateFunc")
service.myHighOrderFunction(privateMethod) 
// In the above line I get a compilation error: required () - Unit, found KFunction<*>
service.myHigherOrderFunction(privateMethod as () -> Unit) 
// In the above line I get the following runtime error:
// ClassCastException: kotlin.reflect.jvm.internal.KFunctionImpl cannot be cast to kotlin.jvm.functions.Function1

I know the test can be done having the privateFunc as public and maybe annotating it with @VisibleForTesting, but what I want is to avoid compromising the design as long as I can.

Any ideas? Thanks in advance!

Alejandro Casanova
  • 3,633
  • 4
  • 31
  • 46

1 Answers1

0

I don't think KFunction and KCallable have any notion of a bound receiver, so they are not invokable (have no operator fun invoke), and therefore don't qualify as functions. So I think you have to wrap the KFunction object in a function to be able to pass it to your higher order function. To call a KFunction, you pass the instance of the receiver class as the first argument.

val serviceMock = MyService()
val sut = SystemUnderTest()
val privateMethod = sut.getPrivateMethod("privateFunc")
service.myHighOrderFunction { privateMethod.call(sut) }

Edit: To internalize the creation of the wrapped function, you could do this:

inline fun <reified T> T.getZeroArgPrivateMethod(name: String): () -> Unit = {
    T::class.declaredMemberFunctions.first {
        it.name == name
    }.apply {
        isAccessible = true
    }.call(this)
}

//...

val serviceMock = MyService()
val sut = SystemUnderTest()
val privateMethod = sut.getZeroArgPrivateMethod("privateFunc")
service.myHighOrderFunction(privateMethod)
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Thanks, but in this case I cannot be sure of what is being invoked, I could pass anything there with the proper signature, for example: service.myHighOrderFunction { Log.d("tag", "this is a huge bug") } I need to make sure in the tests that the proper function is being invoked. – Alejandro Casanova May 21 '21 at 15:54
  • You could do the wrapping of the function internally to the `get` function. See edit. But regardless, I see no way to prevent any type of lambda from being passed to `myHighOrderFunction`. The only way to ensure it only receives a method of an object would be to change the parameter type to a KFunction, and add a parameter for receiver. – Tenfour04 May 21 '21 at 16:02
  • Remember my services is a mock, I'm not executing anything there and I'm not supposed to, what I need is to make sure the param the higher order function receives is the one it is supposed to. – Alejandro Casanova May 21 '21 at 16:23