Skip to content
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

Coroutine/suspend function mocking support #205

Closed
dave08 opened this issue Sep 28, 2017 · 19 comments
Closed

Coroutine/suspend function mocking support #205

dave08 opened this issue Sep 28, 2017 · 19 comments

Comments

@dave08
Copy link

dave08 commented Sep 28, 2017

If you use non-inline lambdas that are not declared as suspend, the runBlocking { } around the test function doesn't help, rather we need to mock { on { runBlocking { suspendFun() } } } for each mock...

@nhaarman
Copy link
Collaborator

Could you post a more elaborate example?

@dave08
Copy link
Author

dave08 commented Sep 28, 2017

interface AccountRepository {
    suspend fun getAccountId(deviceNo: String): Int
}

class SUT(val repo: AccountRepository) {
   fun something() {
        launch(CommonPool) { println(repo.getAccountId("0000")) }
  }
}

// Test function
val repoMock:AccountRepo = mock { onGeneric { runBlocking { getAccountId(any()) } } doReturn 3300 }

val sut = SUT(repoMock)
sut.something()

// Do some assertions on mock

@dave08
Copy link
Author

dave08 commented Sep 28, 2017

If the mock and onGeneric lambdas were either suspend functions or inline, then I wouldn't need to put runBlocking inside the onGeneric for every function, I could just surround the whole mock {} with runBlocking, it would be much cleaner.

@ghost
Copy link

ghost commented Oct 2, 2017

Isn't this gonna be sort of hard to implement if you want to have the library compatible with pre 1.1 kotlin? Maybe soon Gradle magic?

@ghost
Copy link

ghost commented Oct 2, 2017

Or maybe it's enough to just make them inline.

@dave08
Copy link
Author

dave08 commented Oct 2, 2017 via email

@ghost
Copy link

ghost commented Oct 3, 2017

I created a pull request for a suggested fix. Seems like all the previous tests are passing but still waiting for the CI to run.

Not sure if this is exactly what you wanted. There is an example test in there as well.

@dave08
Copy link
Author

dave08 commented Oct 3, 2017 via email

@PaulWoitaschek
Copy link

Any news on this? It's the same with given

    given { runBlocking { mockLoginManager.refreshToken() } }
        .willReturn {
          TokenFactory.create(expired = false)
        }

Creates quite some noise whenever there is a suspending function.

@ghost
Copy link

ghost commented Nov 16, 2017

I tried to switch to inline but that didn't really fix the problem, not sure if I'm smart or knowledgeable enough to fix it. Feel free to pick up where I left off. See my linked pull request.

@nhaarman
Copy link
Collaborator

nhaarman commented Dec 3, 2017

I don't really use coroutines much myself. I can suggest this solution:

fun <T : Any, R> KStubbing<T>.onBlocking(
      m: suspend T.() -> R
): OngoingStubbing<R> {
    return runBlocking { Mockito.`when`(mock.m()) }
}

.. but I don't know if it covers all scenarios. The following test passes:

interface SomeInterface {

    suspend fun foo(): Int
}

@Test
fun test() = runBlocking {
    val m = mock<SomeInterface> {
        onBlocking { foo() } doReturn 42
    }

    val result = m.foo()

    expect(result).toBe(42)
}

@bohsen
Copy link

bohsen commented Jan 11, 2018

The above solution works.

@ZakTaccardi
Copy link

what if I want to control when the suspending function returns? is this possible?

@carterhudson
Copy link

carterhudson commented Jun 29, 2018

@nhaarman
So, "mock" is private in KStubbing, thus that extension function doesn't seem to be an option. Would I need to clone the project locally to implement this, or is it going to be in a future release? In my case, it helped expose that my domain and data layers should probably return Deferred instances instead of making suspend functions part of their API, but I can imagine this is still a pain point for someone else somewhere else.

@juanchosaravia
Copy link

Pointing to this version I was able to use directly the onBlocking function:
"com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0-alpha04"

I tried using latest version:
"com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0-RC1"
but onBlocking is not available anymore and I don't know if there is another function that is replacing this.

St4B added a commit to St4B/Android-CleanArchitecture-Kotlin that referenced this issue Feb 5, 2019
@mochadwi
Copy link

mochadwi commented Mar 29, 2020

.. but I don't know if it covers all scenarios. The following test passes:

interface SomeInterface {

    suspend fun foo(): Int
}

@Test
fun test() = runBlocking {
    val m = mock<SomeInterface> {
        onBlocking { foo() } doReturn 42
    }

    val result = m.foo()

    expect(result).toBe(42)
}

I cannot find this method signature in your library @nhaarman , is this an assertion/verify mocking?

EDITED:
I cannot find expect(result).toBe(42) method ..

@nhaarman
Copy link
Collaborator

nhaarman commented Mar 29, 2020

.. but I don't know if it covers all scenarios. The following test passes:

interface SomeInterface {

    suspend fun foo(): Int
}

@Test
fun test() = runBlocking {
    val m = mock<SomeInterface> {
        onBlocking { foo() } doReturn 42
    }

    val result = m.foo()

    expect(result).toBe(42)
}

I cannot find this method signature in your library @nhaarman , is this an assertion/verify mocking?

See https://github.com/nhaarman/expect.kt

@mochadwi
Copy link

Nice thanks a lot @nhaarman

@MedetZhakupov
Copy link

MedetZhakupov commented Jan 5, 2021

I don't really use coroutines much myself. I can suggest this solution:

fun <T : Any, R> KStubbing<T>.onBlocking(
      m: suspend T.() -> R
): OngoingStubbing<R> {
    return runBlocking { Mockito.`when`(mock.m()) }
}

.. but I don't know if it covers all scenarios. The following test passes:

interface SomeInterface {

    suspend fun foo(): Int
}

@Test
fun test() = runBlocking {
    val m = mock<SomeInterface> {
        onBlocking { foo() } doReturn 42
    }

    val result = m.foo()

    expect(result).toBe(42)
}

While this might work fine for interface's suspend function it's not working for me in mocking below suspend function type

someFunction: suspend () -> Boolean

It throws NullPointerException. If I remove suspend keyword and use simple mocking like

on { invoke() }.doReturn { true }

it also throws NullPointerException but when I change it to

onGeneric { invoke() }.doReturn { true }

then it works fine. In suspend case I am not sure what might be the alternative for that. Anyone has any clue how I can approach this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants