0

I am wondering if I can do this mock

Deferred<Response<Void>>

I am new at Kotlin and at mock but I have unsuccessfully made it.

So far I have done this:

I have a client interface:

class MyClient {

@POST("/names")
    @FormUrlEncoded
    fun names(
        @Field("username") username: String
    ): Deferred<Response<Void>>

}

On my tests:

lateinit var myClient : MyClient

@Before
fun setUp() {
    client = mockk<MyClient>(relaxed = true)
    val response = mockk<Response<Void>>(relaxed = true)
    every { myClient.names(username) }.returns(mockedResponse)

}

I've found many posts about Response (like this Handle empty response with retrofit and rxjava 2.x ) but not really sure how to handle the "Deferred"

or some about deferred (cannot implement the answer of this. async cannot be resolved in my project

When using kotlin coroutines, how do I unit test a function that calls a suspend function?

Is this possible? what am I missing?

The service I am testing:

class MyService {

    fun doSomething(username: String) {
       myClient().names(username).await()
    }
}
  • 1
    Maybe you can show us a more complete example of your code? There are several things that won't compile in your snippets. Where does the username come from here: myClient.names(username)? Is doSomething() in reality a suspend function or where does the needed coroutine come from? – Florian Gutmann Jun 12 at 13:25
  • 1
    I just disccovered the poor error handling and while get in the debugger I see the precise error that is more related with other issues I think "An exception occurs during Evaluate Expression Action : Backend Internal error: Exception during code generation Cause: Can not generate outer receiver value for class.." will need to investigate further, thanks for the help though! – jpganz18 Jun 12 at 14:04
2

You are trying to return a Response<Void> where a Deferred<Response<Void>> would be required.

The deferred is basically the result of a job that may have not yet completed. When the deferred isCompleted, the result is ready. In your case you most likely want to have a Deferred that is already completed with the response.

To create a Deferred that is already completed with some value you can use the CompletableDeferred(value: T) function.

Here is a full example of how to return a completed deferred from a client Mock.

interface GithubClient {
    @GET("users/{username}/repos")
    fun listRepos(@Path("username") username: String): Deferred<Response<Void>>
}

class RepoService(val client: GithubClient) {
    suspend fun doSomething(username: String): Response<Void> {
        return client.listRepos(username).await()
    }
}

class GithubClientTests {

    private lateinit var client: GithubClient

    @BeforeTest
    fun setUp() {
        // setup the mocked client
        client = mockk<GithubClient>(relaxed = true)
        val mockResponse = mockk<Response<Void>>(relaxed = true)
        every { client.listRepos(any()) } returns CompletableDeferred(mockResponse)
    }

    @Test
    fun testMockClient() = runBlocking<Unit> {
        // create the service with the mocked client
        val service = RepoService(client)
        val response = service.doSomething("octocat")

        // response is not successful, because we didn't specify any behavior for the mocked response
        assertFalse { response.isSuccessful }
    }
}
  • Thank you!! In this case this works but now I get an exception "java.lang.ClassCastException: java.lang.Object cannot be cast to retrofit2.Response" is this because the mocked response? it breaks on the line when I call the client on my service in this way -> myClient.names(username).await() – jpganz18 Jun 12 at 12:08
  • Probably the mockedResponse object is not of type retrofit2.Response. – Florian Gutmann Jun 12 at 12:12
  • I get this error "An exception occurs during Evaluate Expression Action : Backend Internal error: Exception during code generation Cause: Inconsistent state: expression saved to a temporary variable is a selector File being compiled at position: mock:///fragment.kt The root cause was thrown at: ExpressionCodegen.java:286" – jpganz18 Jun 12 at 12:19
  • I updated my question, maybe you can take a look – jpganz18 Jun 12 at 12:32
  • In this case it works perfectly, but unfortunately I am testing over a service that calls the client (as described) and the method inside that service makes a call client.listRepos("octocat").await() and I think that is the problem, somehow the mock there gets crazy :s – jpganz18 Jun 12 at 13:10
1

I would say that you're missing an await:

@Before
fun setUp() {
    client = mockk<MyClient>(relaxed = true)
    val response = mockk<Response<Void>>(relaxed = true)
    every { myClient.names(username).await() /* this would return an response instead of deferred */ }.returns(mockedResponse)
}

Let me know if this helps!

  • Thank you! but in this case, it works but it tells me "Suspension functions can only be called within coroutine body", any idea what Id have to do to fix it? I moved it to my unit test function @Test fun test() = runBlocking {..} but the problem persists – jpganz18 Jun 12 at 12:00
  • yes, you might want to look into runBlockingTest: kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test – r2rek Jun 12 at 12:03
  • This solution mocks a smaller part of the code. It only mocks the result of the await() function. For example if the client code does not call await() but isCompleted or any other function of the Deferred, it will fail. – Florian Gutmann Jun 12 at 12:08
  • Sure, it would fail on isCompleted, but it does solve author's problem - your solution works as well. In this specific case you shouldn't worry about other methods being called. Have a good one! – r2rek Jun 12 at 12:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.