Kotlin Unit Testing Retrofit (KUTR)

In this article, we will test our ViewModel in our sample Android app which contains the logic to request employee details using Retrofit. We have the following dependencies added to our build.gradle file:

    //for retrofit.
    implementation 'com.squareup.retrofit2:retrofit:2.7.1'
    implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
    implementation 'android.arch.lifecycle:extensions:1.1.1'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

    //for testing retrofit.
    testImplementation("com.squareup.okhttp3:mockwebserver:4.7.2")

Our ViewModel looks like this:

class RequestViewModel : ViewModel() {

    private val result = MutableLiveData<Resource<MainData>?>()

    fun fetchDetails() {
        val apiHelper = ApiHelperImpl(RetrofitBuilder.apiInterface)
        apiHelper.getEmployeeDetails()
            .enqueue(object : Callback<MainData> {
                override fun onFailure(call: Call<MainData>, t: Throwable) {
                    result.postValue(Resource.error("Something went wrong!",null))
                }

                override fun onResponse(call: Call<MainData>, response: Response<MainData>) {
                    result.postValue(Resource.success(response.body()))
                }
            })
    }

    fun getDetail(): LiveData<Resource<MainData>?> {
        return result
    }
}

So, to test the above ViewModel, we will use MockWebServer of square. First, we start by setting the expected .json response. We have integrated the following APIs in our application:

http://dummy.restapiexample.com/api/v1/employees

So, we add the following .json file to the resources directory which contains only the success and failure responses from the above API.

Moving on to our testing package where we will actually write unit tests to test our RequestViewModel. But before that, we need to write the code to read the above .json file.

class MockResponseFileReader(path: String) {

    val content: String

    init {
        val reader = InputStreamReader(this.javaClass.classLoader?.getResourceAsStream(path))
        content = reader.readText()
        reader.close()
    }
}

Now, let’s create a RequestViewModelTest.kt test to test the Retrofit logic present in the RequestViewModel class. In the setUp() method let’s initialize mockito, RequestViewModel instance, register our observer and the important stuff from this article, initialize MockWebServer and start the same. With this we also initialize ApiHelper so that we can actually fetch the employee details:

@Before
fun setUp() {
        MockitoAnnotations.initMocks(this)

        viewModel = RequestViewModel()
        viewModel.getDetail().observeForever(apiEmployeeObserver)

        mockWebServer = MockWebServer()
        mockWebServer.start()
        apiHelper = ApiHelperImpl(RetrofitBuilder.apiInterface)
}

Let's create our first test to check the content of the success_response.json file we created above, the content should not be null and if we run the following, it has passed successfully:

@Test
fun `read sample success json file`(){
        val reader = MockResponseFileReader("success_response.json")
        assertNotNull(reader.content)
}

In the next test, let's fetch the employee details and check if the response code '200' from the server matches the response code present in success_response.json which is the expected response:

@Test
fun `fetch details and check response Code 200 returned`(){
        // Assign
        val response = MockResponse()
            .setResponseCode(HttpURLConnection.HTTP_OK)
            .setBody(MockResponseFileReader("success_response.json").content)
        mockWebServer.enqueue(response)
        // Act
        val  actualResponse = apiHelper.getEmployeeDetails().execute()
        // Assert
        assertEquals(response.toString().contains("200"),actualResponse.code().toString().contains("200"))
}

Finally, the above test has passed. In the next test, let's take the status from success_response.json and compare it with the actual status we get from the server:

@Test
fun `fetch details and check response success returned`(){
        // Assign
        val response = MockResponse()
            .setResponseCode(HttpURLConnection.HTTP_OK)
            .setBody(MockResponseFileReader("success_response.json").content)
        mockWebServer.enqueue(response)
        val mockResponse = response.getBody()?.readUtf8()
        // Act
        val  actualResponse = apiHelper.getEmployeeDetails().execute()
        // Assert
        assertEquals(mockResponse?.let { `parse mocked JSON response`(it) }, actualResponse.body()?.status)
}

If you notice above, we are calling a method named  parse mocked JSON response, which basically parses the JSON response present in success_response.json.

And yes, the above tests have also passed, we have got “success” from both. Finally, don’t forget to stop the MockWebServer in the tearDown() method and unregister the observer we registered in the setUp() method:

@After
fun tearDown() {
        viewModel.getDetail().removeObserver(apiEmployeeObserver)
        mockWebServer.shutdown()
}

Post a Comment

Previous Next

نموذج الاتصال