3

I had to write unit test for my Repository class. I am working on a REST app and I am using Retrofit for communicating with my REST api.

On my ApiInterface I have the following code:

interface ApiInterface {
    /*
    Login to the api
     */
    @POST("api/auth/login")
    fun login(@Body loginFields: LoginFields): Call<LoginResponse>
}

The LoginResponse and LoginFields above are simple data classes

I have my Repository:

class LoginRepository(private val apiInterface: ApiInterface) : ILoginRepository {

    companion object {

        val TAG = LoginRepository::class.java.simpleName
        private var loginRepository: LoginRepository? = null
        private val apiInterface: ApiInterface =
            RetrofitService.createService(ApiInterface::class.java)

        val instance: LoginRepository
            get() {
                if (loginRepository == null) {
                    loginRepository = LoginRepository(apiInterface)
                }
                return loginRepository as LoginRepository
            }
    }

    override fun getUserLogin(loginFields: LoginFields): LiveData<LoginResponse> {

        val loginResponse = MutableLiveData<LoginResponse>()

        apiInterface.login(loginFields).enqueue(object : Callback<LoginResponse> {
            override fun onResponse(
                call: Call<LoginResponse>,
                response: Response<LoginResponse>
            ) {
                if (response.isSuccessful) {
                    loginResponse.postValue(response.body())
                } else {
                    loginResponse.value = null
                    Log.e(TAG, response.errorBody().toString())
                }
            }

            override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
                loginResponse.value = null
                Log.e(TAG, " Failure getting the logged in user")
                t.printStackTrace()
            }
        })
        return loginResponse
    }
}

The ILoginRepository is a simple interface :

interface ILoginRepository {
    fun getUserLogin(loginFields: LoginFields): LiveData<LoginResponse>
}

From what I have heard so far, Unit tests are meant for single units of the code and should not care for other classes. So what I did was create a FakeRepository which implements ILoginRepository and is as follows:

class FakeRepository : ILoginRepository {
    override fun getUserLogin(loginFields: LoginFields): LiveData<LoginResponse> {
        val liveData: MutableLiveData<LoginResponse> = MutableLiveData()
        liveData.postValue(
            LoginResponse(
                "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImU3NjIxNjBmNTMxN2NlMmZiMDc0NzcxODQ1MTJjZGVjZmE4NmVkNmI3Njg5ZmM1N2UzOWYyMDJhNTQyODZlOTZlN2I3OWZjMDZhZTliODYyIn0.eyJhdWQiOiI1IiwianRpIjoiZTc2MjE2MGY1MzE3Y2UyZmIwNzQ3NzE4NDUxMmNkZWNmYTg2ZWQ2Yjc2ODlmYzU3ZTM5ZjIwMmE1NDI4NmU5NmU3Yjc5ZmMwNmFlOWI4NjIiLCJpYXQiOjE1NzQwNjA4NzUsIm5iZiI6MTU3NDA2MDg3NSwiZXhwIjoxNjA1NjgzMjc0LCJzdWIiOiIzNiIsInNjb3BlcyI6W119.S77D2pG7ScQn7ufyyVG2fhnydKWJUUv3Khotqt4K41ylP_7VWqNmIzUr0ZfeuryWvRHj0qP8kEG4hIbbfmFOnEHQBwTCVEyV7LNaazYLg9VIvXeHiQ99vZHVSyBwy-ENY772YxpBHCPy6aG5ziQM1LK357mOoIQiucLXGe1lQkwXSlqULuEqt-5tsDAgnMPz48d6cEzVF9muU2PjgwNThcPUyNkivlzHuzsj7pQWum7bALHPzcoQRNGzEzhICShCtEBQ_T0VbMakcY2Iwl2rQuxWPBiFjpNv69VfXjAxXuYkbRpRfbIiNIBuaHWOlvkOHQAbUELg6Nd88L78UN4z91SBhN6JVB3kG8ibHSHT7ip4ruLiXSRtDROX3I_mpMx_7gcqK80L2FqxnznTLbg39Mgkfsu56TbGd2NTtHBcif66SUPXsrIM-kGIvsajUz-x3VHFJvJvAvZ5zSKMq5c4pTkDYCv4g6-o7fEX3bIT94m89K5drAub8iluRFyXNnhSMi29JpSxU6LeMmuaZGvQssm170oY76ZOMe3xwVMzWatXVHtpsl51M2iC2JhXq9Z-pxd1ZAx8_HVbG01OOpqcqKoFbTFtgsl8mIxDsU91aBDHN_xqXO6EUTdm7Gz4TOo1_C5m1R5nYWuGngAXfIFzlF4L6gBUokJPs8KDPxNrBdk",
                "Bearer",
                "2020-11-18 12:52:54",
                User(
                    35,
                    "Prashanna Bhandary",
                    loginFields.email,
                    "dd58a617ea618010c2052cb54079ad67.jpeg",
                    "98********",
                    "test address 01",
                    1,
                    "yes",
                    "2019-08-30 04:56:43",
                    "2019-08-30 05:14:47",
                    0
                )
            )
        )
        return liveData
    }
}

You can see what I am doing here.

Now to test the Repository, I made a unit test class like following:

class LoginRepositoryTest {
    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()
    //data input
    val loginFields = LoginFields("test@test.com", "test@123")
    //expected
    val correctLoginResponse = LoginResponse(
        "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImU3NjIxNjBmNTMxN2NlMmZiMDc0NzcxODQ1MTJjZGVjZmE4NmVkNmI3Njg5ZmM1N2UzOWYyMDJhNTQyODZlOTZlN2I3OWZjMDZhZTliODYyIn0.eyJhdWQiOiI1IiwianRpIjoiZTc2MjE2MGY1MzE3Y2UyZmIwNzQ3NzE4NDUxMmNkZWNmYTg2ZWQ2Yjc2ODlmYzU3ZTM5ZjIwMmE1NDI4NmU5NmU3Yjc5ZmMwNmFlOWI4NjIiLCJpYXQiOjE1NzQwNjA4NzUsIm5iZiI6MTU3NDA2MDg3NSwiZXhwIjoxNjA1NjgzMjc0LCJzdWIiOiIzNiIsInNjb3BlcyI6W119.S77D2pG7ScQn7ufyyVG2fhnydKWJUUv3Khotqt4K41ylP_7VWqNmIzUr0ZfeuryWvRHj0qP8kEG4hIbbfmFOnEHQBwTCVEyV7LNaazYLg9VIvXeHiQ99vZHVSyBwy-ENY772YxpBHCPy6aG5ziQM1LK357mOoIQiucLXGe1lQkwXSlqULuEqt-5tsDAgnMPz48d6cEzVF9muU2PjgwNThcPUyNkivlzHuzsj7pQWum7bALHPzcoQRNGzEzhICShCtEBQ_T0VbMakcY2Iwl2rQuxWPBiFjpNv69VfXjAxXuYkbRpRfbIiNIBuaHWOlvkOHQAbUELg6Nd88L78UN4z91SBhN6JVB3kG8ibHSHT7ip4ruLiXSRtDROX3I_mpMx_7gcqK80L2FqxnznTLbg39Mgkfsu56TbGd2NTtHBcif66SUPXsrIM-kGIvsajUz-x3VHFJvJvAvZ5zSKMq5c4pTkDYCv4g6-o7fEX3bIT94m89K5drAub8iluRFyXNnhSMi29JpSxU6LeMmuaZGvQssm170oY76ZOMe3xwVMzWatXVHtpsl51M2iC2JhXq9Z-pxd1ZAx8_HVbG01OOpqcqKoFbTFtgsl8mIxDsU91aBDHN_xqXO6EUTdm7Gz4TOo1_C5m1R5nYWuGngAXfIFzlF4L6gBUokJPs8KDPxNrBdk",
        "Bearer",
        "2020-11-18 12:52:54",
        User(
            35,
            "Prashanna Bhandary",
            loginFields.email,
            "dd58a617ea618010c2052cb54079ad67.jpeg",
            "98********",
            "test address 01",
            1,
            "yes",
            "2019-08-30 04:56:43",
            "2019-08-30 05:14:47",
            0
        )
    )
    //class under test
    private lateinit var loginRepository: FakeRepository
    @Before
    fun setUp() {
        loginRepository = FakeRepository()
        ServiceLocator.loginRepository = loginRepository
    }
    @Test
    fun correctLoginFields_geUserLogin_shouldGiveLoginResponse() {
        //when
        val loginResponse = loginRepository.getUserLogin(loginFields).value
        //then
        assertEquals(correctLoginResponse, loginResponse)
    }
}

So I guess what my question is: How valid is my Unit Test? Is this how we are supposed to unit test things? Does this unit test provide me any value?

ravi
  • 899
  • 8
  • 31
  • I will recommend you to have a look at mock web server from Retrofit. You can unit test your actual repository by enqueuing actual responses without making network calls or faking anything: https://github.com/square/okhttp/tree/master/mockwebserver – Giorgos Neokleous Nov 21 '19 at 10:44
  • Hey @GiorgosNeokleous , Thank you for pointing me to the mockwebserver. I had actually came across this earlier. I sort of did not bother looking deeper. But now that my unit test has ended up this way, maybe I should start looking deeper into it. Thanks. In the mean time. how valid do you think my test is? – ravi Nov 21 '19 at 11:05

1 Answers1

2
  • Does this unit test provide me any value? : No value (until you use FakeRepository in production)

This test code is testing FakeRepository. I think you want to test LoginRepository which is an ILoginRepository implementation. Because you're not going to use FakeRepository in production, isn't it?

I'll explain briefly how to unit test. You need to instantiate the class Foo you want to test and mock the classes it depends on.

Let's see your code. The important part is below code.

interface ApiInterface {
    @POST("api/auth/login")
    fun login(@Body loginFields: LoginFields): Call<LoginResponse>
}

class LoginRepository(private val apiInterface: ApiInterface) : ILoginRepository { ... }

So in your case, you have to mock ApiInterface. And instantiate LoginRepository. But mocking ApiInterface is too hard because it depends on Retrofit class such as Call.

To solve these problems simply, you need OkHttp MockWebServer.

galcyurio
  • 1,776
  • 15
  • 25
  • Hello. thank you for your answer. How would I instantiate the `LoginRepository` if I am to not use the `ApiInterface`? I get how the OkHttp MockWebServer works but how do I instantiate my LoginRepository? – ravi Nov 22 '19 at 05:40
  • You cannot instantiate a `LoginRepository` without using `ApiInterface`. Perhaps you want to mock without creating an ApiInterface, right? If so, you don't need mocking, just create `ApiInterface` like your production code. And then, create a virtual server through MockWebServer. – galcyurio Nov 25 '19 at 03:09
  • Hi, I can instantiate the repository without creating an instance of ApiInterface in my latest code where I am using ServiceLocator or at least I think I can. When you say " create a virtual server through MockWebServer". did you mean OkHTTP MockWebServer or some other service like Postman's Mock Web Server? This whole thing is really confusing right now – ravi Nov 25 '19 at 11:20
  • That's it. You instantiate it via ServiceLocator. "MockWebServer" means OkHttp's [MockWebServer](https://github.com/square/okhttp/tree/master/mockwebserver). I understand your confusing. Perhaps most developers will think a lot about testable architecture. – galcyurio Nov 26 '19 at 08:02
  • 1
    `git clone https://github.com/galcyurio/OkHttpTestSample.git` I created sample project based on your code. I hope it helps you. – galcyurio Nov 26 '19 at 08:04
  • Hey. Thank you for your time. I am looking at your code right now. I noticed that you have included `shadowOf()` and `@Config(sdk = [28])` which are from Robolectric. However when I ran the test (on my own code after changing my test based on your repo), I am getting error saying that Robolectric tests require java 9. Now I had Java 9 but had to downgrade to Java 8 because of JAXB Exception (related to xml binding) which I think is not included in the Java 9 and was not letting me build from CLI – ravi Nov 26 '19 at 09:07
  • Replace sdk version to 29. Robolectric insists on Java 9 to match Android 10. – galcyurio Nov 26 '19 at 09:47
  • And Java9 still contains JAXB API. You can add module using `JAVA_OPTS` variable. See [here](https://stackoverflow.com/a/43574427/6686126). – galcyurio Nov 26 '19 at 09:55
  • So `@Config(sdk = [28])` is required to run the test on SDK 28 because running on 29 would require java 9. I followed your github repository and wrote test for my LoginRepository but now for some weird reason I'm getting an `AssertionError`. The error message is not so helpful either. – ravi Nov 26 '19 at 12:35