-
Notifications
You must be signed in to change notification settings - Fork 200
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
Testing suspending function types fails when invocation is done in different context #247
Comments
This is not caused by mockito-kotlin. Its the way the coroutines are used. You start two different coroutines by using runblocking twice. This will create two different coroutines with different contexts. The right way would be to run it in one coroutine (this will pass):
|
…pending function. Closes mockito#247.
…pending function. Closes mockito#247.
…pending function. Closes mockito#247.
I don't think "the right way to use coroutines" matters here.
And in my example it HAS been called. So that test should succeed. |
Add logging to your example and see for yourself, ie.
Remember to add debugging JVM option. The test will fail and print the following followed by the stacktrace:
You will notice that you are inside two different coroutines which can be compared with running in different threads. |
Yes, I understand that. But mockito is supposed to verify if method is called, right? It shouldn't care on what context the method is called. For example Example in the first post is of course not real-world one. I know it is stupid to call |
…pending function. Closes mockito#247.
Maybe related to this: #205 |
I would agree with this, especially given the point about threads that @matejdro made and the fact that this library is written to make working with Mockito and Kotlin easier. Here's another example of this failing: package com.example
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.runBlocking
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
interface SomeInterface {
suspend fun someSuspendFunction(): Boolean
}
class SuspendSpec : Spek({
val mockInterface by memoized { mock<SomeInterface>() }
describe("stubbing a suspend function") {
beforeEachTest {
runBlocking { whenever(mockInterface.someSuspendFunction()) }.thenReturn(true)
}
it("gives you the return value that you asked for") {
val returnValue: Boolean? = runBlocking { mockInterface.someSuspendFunction() }
assertThat(returnValue).isTrue()
}
it("lets you verify invocation of the stubbed function") {
runBlocking { mockInterface.someSuspendFunction() }
runBlocking { verify(mockInterface).someSuspendFunction() }
}
}
}) Both specs fail, but should pass IMO:
|
…pending function. Closes mockito#247.
This is not a bug in Mockito-kotlin. I still believe it comes down to how coroutines work. Suspending functions are not some special type of functions. They are regular functions that use CPS transformation to add an extra parameter of type So for each invocation of The stacktrace actually tells you this.
So just to recap. I don't believe this is an issue for mockito-kotlin to handle, but should be discussed in the coroutine project. Though I actually don't believe there would ever exist an use-case for this, as to me it wouldn't ever make sense to call |
I'm not sure coroutines people can change anything, this is how they made their system. If anything, it should be discussed within regular mockito project. But mockito is java-only and does not really involve kotlin, so next best thing is this project. I propose solution that somehow ignores first argument here (or automatically marks it as
As already stated before, example in the first post is NOT production example. I agree it is completely useless. But at the same time, it is the most simple example one can make for a bug report. This is meant for generic bug report for all verifications with different continuations. |
We can debate what to call this ("bug" or "not a bug") all we want, but it doesn't really matter. The important thing is that we're looking for a way to mock and verify suspend functions. Given that suspend functions are a Kotlin concept, and we're trying to mock/verify them, a Mockito concept, it seems rather fitting that mockito-kotlin provide a way to do this. EDIT: If there's a way to do this that I'm just not aware of, I'm all ears! |
@miketrewartha There is a way - take a look at the tests in the source code. |
@bohsen all of those make sense, but I'm looking for a way to mock a suspend function for a mock I created further up in a test (in some contextual setup), because I don't want to have to redefine everything inside each test or before block: object SubjectClassSpec : Spek({
val mockDependency by memoized { mock<DependencyClass>() }
val subject by memoized { SubjectClass(mockDependency) }
describe("doing something") {
beforeEachTest {
// This doesn't work (not calling someSuspendFunction in a suspend function or coroutine),
// so how can I mock this function without having to redefine my subject and mockDependency
// in this specific beforeEachTest block?
whenever(mockDependency.someSuspendFunction()).thenReturn("some value")
}
// ... some actual assertions in `it` blocks
}
}) I've tried wrapping the |
Here's another example of something related that I'm stuck on: object SubjectClassSpec : Spek({
val mockDependency by memoized { mock<DependencyClass>() }
val subject by memoized { SubjectClass(mockDependency) }
describe("doing something") {
val result = subject.doSomethingThatCallsTheDependencySuspendFunction()
it("uses the dependency in a certain way") {
runBlocking { verify(mockDependency).someSuspendFunction() }
}
}
}) This results in a test failure because the continuations aren't the same. Now that I think about is, I'm wondering if that's a problem that's specific to my setup. I have a class that follows the same general pattern as above and in my actual function (represented by |
Should work even with a Java interface I believe. Try wrapping Like this (not using Java interface though, but shouldn't matter): object SpekTestCoroutines : Spek({
val mockDependency by memoized { mock<Foo>() }
val subject by memoized { Bar(mockDependency) }
describe("doing something") {
runBlocking {
subject.result(42)
it("uses the dependency in a certain way") {
runBlocking { verify(mockDependency).suspending() }
}
}
}
})
interface Foo {
suspend fun suspending(): Int
fun nonsuspending(): Int
}
class Bar(val foo: Foo) {
suspend fun result(r: Int) = withContext(CommonPool) { foo.suspending() }
} This is using spek 2.0.0-alpha.2. Here's a section of my build.gradle: dependencies {
...
testCompile 'org.junit.jupiter:junit-jupiter-api:5.2.0'
testImplementation ('org.spekframework.spek2:spek-dsl-jvm:2.0.0-alpha.2') {
exclude group: 'org.jetbrains.kotlin'
}
testRuntimeOnly ('org.spekframework.spek2:spek-runner-junit5:2.0.0-alpha.2') {
exclude group: 'org.junit.platform'
exclude group: 'org.jetbrains.kotlin'
}
// spek requires kotlin-reflect, can be omitted if already in the classpath
testRuntimeOnly "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
// setup the test task
test {
useJUnitPlatform {
includeEngines 'spek2'
}
} |
It works with the new version of mockito-kotlin, testCompile 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0' |
Still broken for me with |
Sorry, I'm not using mockito-kotlin anymore so I cannot confirm. |
I'm also experienced this in mockito-kotlin I've an interface interface Example {
val onSubmit: suspend () -> Unit
} I need to mock this interface val mockedOnSubmit = mock<suspend () -> Unit>()
val mockedExample = mock<Example> {
on { onSubmit } doReturn mockedOnSubmit
} the verify on verify(mockedOnSubmit, times(1)).invoke() Any input on how to test in this situation? I really don't care the continuation is different, of course it is. I just want to check it is invoked. |
Hi did you used
https://proandroiddev.com/how-to-unit-test-code-with-coroutines-50c1640f6bef
kotlin coroutines test?
…On Tue, 24 Aug 2021 at 14:41, Daniele Segato ***@***.***> wrote:
I'm also experienced this in mockito-kotlin 3.2.0.
I've an interface
interface Example {
val onSubmit: suspend () -> Unit
}
I need to mock this interface
val mockedOnSubmit = mock<suspend () -> Unit>()val mockedExample = mock<Example> {
on { onSubmit } doReturn mockedOnSubmit
}
the verify on mockedOnSubmit fails complaining the continuation is
different.
verify(mockedOnSubmit, times(1)).invoke()
Any input on how to test in this situation? I really don't care the
continuation is different, of course it is. I just want to check it is
invoked.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#247 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACSDTGWIWLQK74VF2LMOIK3T6OHOHANCNFSM4EZ54VLA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&utm_campaign=notification-email>
.
|
@kyodgorbek what is it that you are asking me if I used in that article? I didn't use that article. But I do have my own TestCoroutineDispatcher rule. Can you elaborate on what you think is the cause? By the way... if I change interface Example {
val onSubmit: suspend () -> Unit
} to interface Example {
suspend fun onSubmit()
} it is mockable and works like I expect, so I'm not sure why the lambda wouldn't |
Run this test code:
It should succeed (function was invoked, so verify should pass), but it fails with an error:
Reproduced on versions:
The text was updated successfully, but these errors were encountered: