https://kotlinlang.org logo
#server
Title
# server
l

Laurent Thiebaud

10/24/2023, 12:31 PM
Hi there, I am trying to define a lambda using 2 receivers (at least that is what I think), for tests purpose using mockito-kotlin I currently have the following code
Copy code
mock<MyService> {
    onBlocking {
        with(any<MyContext>()) {
            myMethod(vehicleId, deploymentName)
        }
    }.doReturn(success(lineExecutionStateResource))
}
with
Copy code
context(MyContext)
suspend fun myMethod(...)
This works and this is standard. As I write this piece of code at +100 places, I'd like to refactor it to extend the
onBlocking
method to
onBlockingWithMyContext
As of now, the
onBlocking
method is defined as
Copy code
fun <T : Any, R> KStubbing<T>.onBlocking(
    m: suspend T.() -> R
): OngoingStubbing<R> {
    return runBlocking { Mockito.`when`(mock.m()) }
}
so I'd like to indicate that the context of
m
is
T
and
MyContext
. I found no way to define
m
this way any idea? As the end, the method body should be like
Copy code
return runBlocking { with(any<MyContext>()) { Mockito.`when`(mock.m()) } }
a

AdamW

10/25/2023, 9:59 AM
So just to be clear, you want the lambda in a context, so that it’s invoked like
with(any<SomeContextType>()) {
}
? I guess you can reify a type parameter to be used with
any
, and change the signature of
m
so that it has a context receiver.
c

Clement Petit

10/25/2023, 12:49 PM
Hey! If the goal is to make the context "visible" in the
m
lambda, then the correct way of doing that is to add the
context(MyContext)
part to the lambda type signature. On top of that, you need to pass the context as a regular parameter to the lambda, instead of using
with
. This should be something like this:
Copy code
fun <T : Any, R> KStubbing<T>.onBlocking(
    m: suspend context(MyContext) T.() -> R
): OngoingStubbing<R> {
    val context = any<MyContext>()
    return runBlocking {
        Mockito.`when`(mock.m(context, this))
    }
}
Maybe in the future, using the "parent" context when invoking a lambda with context receivers will be supported, but for now it isn't!
l

Laurent Thiebaud

10/26/2023, 12:09 PM
Thanks for your answer @Clement Petit I didn't find any doc explaning that we may define a lambda like
Copy code
m: suspend context(MyContext) T.() -> R
with the context, for the compilator this is OK ✌️ with this
Copy code
Mockito.`when`(mock.m(context, mock))
However at runtime I get
Copy code
java.lang.ClassCastException: class MyMockClass cannot be cast to class MyContext
I can't figure out what I or kotlin is doing wrong here 🤔
a

AdamW

10/26/2023, 5:11 PM
I don’t see why you couldn’t use
with
here, exactly as you did in the first example. Also, you can reify the context type and mark
m
crossinline if you’re dealing with multiple context types, but I guess you only have
MyContext
?
l

Laurent Thiebaud

10/31/2023, 3:24 PM
Indeed I only have
MyContext
, this is really strange it looks like a bug in the compiler 🤔 I tried every possible combination of context / context receiver but nothing works (either at compile time or run time)
a

AdamW

10/31/2023, 3:25 PM
Strange 🤔 Also when using
with
instead of passing the context as a parameter?
l

Laurent Thiebaud

10/31/2023, 3:28 PM
With this method
Copy code
fun <T : Any, R> KStubbing<T>.onBlockingContext(
    m: suspend context(MyContext) T.() -> R
): OngoingStubbing<R> {
    val context = any<MyContext>()
    return runBlocking {
        with(context) {
            Mockito.`when`(m(this@onBlockingContext.mock))
        }
    }
}
I still have the same runtime issue
Copy code
java.lang.ClassCastException: class MyMockedClass cannot be cast to class MyContext (MyMockedClass and MyContext are in unnamed module of loader 'app')
(I cleaned the message to remove the real class names)
👍 1
Finally created a post on stackoverflow, in case someone has a better idea 😞
👍 1
2 Views