16

Here i got a sample of code in presenter. How do i make write a test for onSuccess and onFailure in retrofit call

public void getNotifications(final List<HashMap<String,Object>> notifications){

        if (!"".equalsIgnoreCase(userDB.getValueFromSqlite("email",1))) {
            UserNotifications userNotifications =
                    new UserNotifications(userDB.getValueFromSqlite("email",1),Integer.parseInt(userDB.getValueFromSqlite("userId",1).trim()));
            Call call = apiInterface.getNotifications(userNotifications);
            call.enqueue(new Callback() {
                @Override
                public void onResponse(Call call, Response response) {
                    UserNotifications userNotifications1 = (UserNotifications) response.body();


                    if(userNotifications1.getNotifications().isEmpty()){
                        view.setListToAdapter(notifications);
                        onFailure(call,new Throwable());
                    }
                    else {
                        for (UserNotifications.Datum datum:userNotifications1.getNotifications()) {
                            HashMap<String,Object> singleNotification= new HashMap<>();
                            singleNotification.put("notification",datum.getNotification());
                            singleNotification.put("date",datum.getDate());
                            notifications.add(singleNotification);
                        }
                        view.setListToAdapter(notifications);
                    }
                }

                @Override
                public void onFailure(Call call, Throwable t) {
                    call.cancel();
                }
            });
        }
    }

}

How do i write unittesting to cover all cases for this piece of code.

Thanks

Michael Meyer
  • 2,179
  • 3
  • 24
  • 33
Jay
  • 473
  • 1
  • 6
  • 19

2 Answers2

34

When you want to test different responses from service (API) it's probably best to mock it and return what you need.

    @Test
    public void testApiResponse() {
      ApiInterface mockedApiInterface = Mockito.mock(ApiInterface.class);
      Call<UserNotifications> mockedCall = Mockito.mock(Call.class);

      Mockito.when(mockedApiInterface.getNotifications()).thenReturn(mockedCall);

      Mockito.doAnswer(new Answer() {
        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
          Callback<UserNotifications> callback = invocation.getArgumentAt(0, Callback.class);

          callback.onResponse(mockedCall, Response.success(new UserNotifications()));
          // or callback.onResponse(mockedCall, Response.error(404. ...);
          // or callback.onFailure(mockedCall, new IOException());

          return null;
        }
      }).when(mockedCall).enqueue(any(Callback.class));

      // inject mocked ApiInterface to your presenter
      // and then mock view and verify calls (and eventually use ArgumentCaptor to access call parameters)
    }
Petr Šabata
  • 635
  • 6
  • 7
  • 2
    Thankyou soo much dude... it really saved my day – Jay Jul 18 '17 at 14:35
  • could you please explain me about Mockito.doAnswer(something..) – Jay Jul 18 '17 at 14:39
  • 1
    You use that notation to specify mock behavior. It's similar to Mockito.when(mockObject).someMethod(any(Parameter.class)).thenReturn(returnValue); but this one must be used for functions with no (void) return type. See https://testing.googleblog.com/2014/03/whenhow-to-use-mockito-answer.html. – Petr Šabata Jul 20 '17 at 07:22
  • This works well, however there is race condition that will cause tests to fail. Once enqueue is called, the unit test will continue and try to verify things that haven't been called yet. Is there a way to get the callback to finish first? – ShrimpCrackers Oct 20 '18 at 23:00
  • @ShrimpCrackers Are you sure you're injecting the mocked ApiInterface to your production code? Because the code introduced above only specifies the behavior of the mock, it doesn't actually execute anything. – Petr Šabata Nov 01 '18 at 14:35
  • This answer deserve bounty. This is very common case and usually skipped or done in wrong way. But we as developers are writing good architecture code for test, and not the unit test for what intent the architecture was followed. I find it hard to get answers about unit testing on forums, which in my opinion shows devs are writing code for tests, and lack the ability or whatever it is to write tests. Great answer man, wish I could give you the bounty :) – Talha Dec 22 '18 at 08:23
  • Can you post entire method ? – akshay bhange Mar 06 '19 at 10:26
  • I don't have it. This was just an example devised to sketch the idea out. It also heavily depends on the implementation details... Hope you will solve it eventually. – Petr Šabata Mar 07 '19 at 13:46
  • @PetrŠabata how can I achieve the same API testing with mockwebserver? where I mock the web server and when the enqueue gets called it will give mocked server response. Right now with mockwebserver when the enqueue is getting called, because of callbacks the operation is getting performed asynchronously. How can I combine mocking web server with callback synchronization? Do you have any idea? Thanks in advance. – Kavita Patil Sep 23 '19 at 19:33
  • Hello @Kavita_p, I think that your question might by answered here: https://stackoverflow.com/questions/24489882/mockwebserver-and-retrofit-with-callback – Petr Šabata Sep 25 '19 at 22:10
  • Hi @PetrŠabata , thanks for the response, but still I didn't find the solution for this. Can you please have a look https://stackoverflow.com/q/58367198/6532155 question also. – Kavita Patil Oct 13 '19 at 19:46
  • How to mock Class ? – Yusuf Eka Sayogana Dec 01 '19 at 13:16
  • https://stackoverflow.com/a/1652738/5011335 @YusufEkaSayogana – Petr Šabata Jan 13 '20 at 08:58
  • @PetrŠabata How to test Observable....subscribe(new Disposable)onNext(){}...onError(){} – Girish Apr 28 '20 at 12:48
1

For those looking for an answer using Kotlin and MockK:

Assume you have something like this:

class ApiService(private val client: OkHttpClient) {
    
    fun makeApiCall() {
        val url = "https://someendpoint.com.br/"
        val request = Request.Builder().url(url).build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, exception: IOException) {
                //Logic to handle Failure
            }

            override fun onResponse(call: Call, response: Response) {
                //Logic to handle Success
            }
        })
    }
}

You can test this using Junit 5 and mockK

class ApiServiceTest {

    private lateinit var client: okhttp3.OkHttpClient
    private lateinit var apiService: ApiService

    @BeforeEach
    fun setup() {
        // Setup a new mock for each test case
        client = mockk(relaxed = true)
        apiService = ApiService(client)
    }

    @Test
    fun `test with mockedCallback`() {
        val mockedCall = mockk<Call>(relaxed = true) 

        every { mockedCall.enqueue(any()) } answers {
           //Get the callback from the arguments
            val callback = args[0] as Callback 
           // Create a fakeRequest 
           val fakeRequest =    okhttp3.Request.Builder().url("https://someendpoint.com.br/").build()
            // Create the response you need to test, it can be failure or success
            val errorResponse = Response.Builder()
                    .request(fakeRequest)
                    .protocol(Protocol.HTTP_1_1)
                    .code(400)
                    .message("Error")
                    .build()
            //Call the callback with the mocked response you created.
            callback.onResponse(mockedCall, errorResponse)
        }

        // Setup the client mock to return the mocked call you created
        every {
            client.newCall(any())
        } returns mockedCall

       apiService.makeApiCall()
       
       // Verify whatever you need to test your logic to handle each case.
    }

}
manoellribeiro
  • 187
  • 2
  • 5