Unable to figure out how to mock higher order functions using spock. Example code snippets:
import jakarta.inject.Singleton
@Singleton
class SomeClass {
fun bar(function: () -> Unit) {
function()
}
}
@Singleton
class SomeOtherClass {
fun foo() {
println("foo")
}
}
@Singleton
class ClassUnderTest(
private val someClass: SomeClass,
private val someOtherClass: SomeOtherClass,
) {
fun test() {
// someOtherClass::foo type is KFunction0<Unit>
someClass.bar(someOtherClass::foo)
}
}
Spock test:
class ClassUnderTestSpecification extends Specification {
def someClass = Mock(SomeClass)
def someOtherClass = Mock(SomeOtherClass)
def classUnderTest = new ClassUnderTest(someClass, someOtherClass)
def 'test class'() {
when:
classUnderTest.test()
then:
// someOtherClass::foo type is Groovy Closure
// fails, as it should be the Kotlin KFunction
1 * someClass.bar(someOtherClass::foo)
0 * _
}
}
As stated in the few comments in the snippets, someOtherClass::foo returns differently between the Kotlin code (KFunction) and Groovy/Spock (Groovy Closure). I haven't found anyways to get the actual KFunction for mocking, it should really just be a reference to a function so I feel the mocking shouldn't be all that hard I am just missing something here.
Have tried trying to cast Groovy closure to KFunction with no luck (didn't expect it to work), tried using just the plain SomeOtherClass::foo instead of the specific mock instance but still was a Groovy closure, etc. All paths have lead to :
Too many invocations for:
0 * _ (1 invocation)
Matching invocations (ordered by last occurrence):
1 * someClass.bar(fun com.example.package.SomeOtherClass.foo(): kotlin.Unit) <-- this triggered the error
I am not a Kotlin user. At first, I was wondering why my error message differed from yours:
The reason is explained here: I had to add
kotlin-reflectas a dependency to my sample Kotlin module.Solution 1: Use Kotlin reflection to determine argument details
Unfortunately, there does not seem to be a good way to use the Kotlin function reference in Groovy. So the best we can do is
Function0<Unit>andtoString()output as good as we can.Here is a variant which matches both the variant without and with
kotlin-reflect:Or, if you prefer substring matching to regex matching:
if you want to relax your argument constraint to just check the type, you can of course simply use:
There might be a better, more precise way to solve this, but someone more versed in both Groovy and Kotlin would have to answer that.
Solution 2: Use Spock spy to pass through method call to secondary mock and verify on the latter being called
Like Leonard already said in his comment, you really seem to be over-specifying your feature in a very rigid way, making the specification brittle with regard to refactoring in your subject under specification.
Anyway, you can use something I would call a hybrid between a mock and a spy, i.e. a test double wrapped around a real object instance, but returning mock responses by default where a normal spy would pass through all method calls. Then, you pass through the one method call you are interested in to the wrapped instance, which in this case is yet another mock that you can verify the method call on.
Of course, for this simple setup, the default response + override solution is unnecessary, because
SomeClassonly has one method anyway. But if your real class has multiple methods and you really want to mock it as much as possible, because it is a test dependency rather than the subject under specification, the above is a way to achieve that.