Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test retrofit call?

For Example I have a retrofit interface such as:

interface SampleService {
    fun getSomething(@body someBody: SomeBody)
}

Now I have a class which uses this interface such as:

class UserRequester(val service: SampleService) {
     fun doGetSomething(someValue: String) {
         val response = service.getSomething(SomeBody(someValue))
         // ...
     }
 }

I want to test this class but dont know how to mock it.

I'm trying the following:

val mockSampleService = mock()
val userRequester = UserRequester(mockSampleService)
val requestBody = SomeBody(someString))
  when(mockSampleService.getSomething(requestBody)).return(myExpectedValue)
....

My problem is that since I create the request object inside the function, I could not make the mock when().thenReturn() to work since i am technically passing two different object.

How should I test this? Thanks in advance.

like image 354
Archie G. Quiñones Avatar asked Dec 10 '18 14:12

Archie G. Quiñones


2 Answers

The mocking problem (UserRequester)

You are not able to mock the mockSampleService method because your class is creating the SomeBody object and is different from the SomeBody object you are creating in your test.

Now you have 2 options:

  1. Use Mockito.any() in your test, in this way you basically say that whatever your method is gonna use as parameter you will return the mocked behaviour
  2. Use a factory that given a someString returns you a SomeObject like this:

// the factory
class SomeObjectFactory{

    fun createSomeObject(someString: String): SomeObject {
        return SomeObject(someString)
    }

}

//the class
class UserRequester(
val service: SampleService, val factory: SomeObjectFactory
) {
     fun doGetSomething(someValue: String) {
         val response = service.getSomething(factory.createSomeObject(someValue))
         // ...
     }
 }

//the test
class MyTest{

    @Test
    fun myTestMethod(){
        val mockSampleService = mock()
        val factory = mock()
        val someBody = mock()
        val userRequester = UserRequester(mockSampleService, factory)
        `when`(factory.createSomeObject(someString)).thenReturn(someBody)

  `when`(mockSampleService.getSomething(someBody)).thenReturn(myExpectedValue)
    //rest of the code
    }

}

The second approach is the cleanest one.

Testing Retrofit calls (SampleService)

I wouldn't unit test a Retrofit call.

When you are dealing with frameworks, apis, databases, shared preferences is always preferable to do integration tests instead of unit tests.

In this way you are actually testing that your code is working with the outside world.

I suggest you to test Retrofit calls with MockWebServer (it's a library from Square, the same company that developed OkHttp and Retrofit).

This read may be also helpful.

like image 115
Bronx Avatar answered Sep 23 '22 19:09

Bronx


Probably SomeBody is a plain value object, since Retrofit requests work with value objects. If you define the equals method for the SomeBody class then the eq matcher will work, and you can write using mockito-kotlin:

whenever(mockService.getSomething(eq(SomeBody(someString)))).thenReturn(stubbedResult)

Actually, you can omit the eq matcher, Mockito will use the equals method for matching.

If SomeBody is a Kotlin data class then the equals method is automatically defined by comparing the fields.

If for some reason you don't want to rely on equals, then you can use the argThat matcher defined in mockito-kotlin:

whenever(mockService.getSomething(argThat { theField == someValue })).thenReturn(stubbedResult)
like image 36
Janos Breuer Avatar answered Sep 22 '22 19:09

Janos Breuer