Android Unit Test with Retrofit and Mockito

12,528

Solution 1

The article isn't very clear as it misses out the setup steps. By visiting the GitHub project linked in the article, you can see the full source code which explains those missing steps:

1) The code samples are extracted from a test class testing a specific activity. As part of the setup (i.e. in @Before), it replaces the Activity's reference to a GitHub API implementation with a mock one. It then calls the Activity's onCreate().

2) During onCreate(), the activity makes a call to the now-replaced GitHub API, passing in its Callback object.

Those first two steps explain why the Mockito.verify(mockApi).repositories(Mockito.anyString(), cb.capture()); step at the beginning of each test works. As the test is run after @Before, the mockApi has indeed had a call on its repositories() method.

The rest of the code is easier to understand once that's in place. As he's only created a mockApi, but not changed the actual Callback being used, the activity's content is changed. The rest of the code then verifies that those changes have taken place, either by checking a ListView or the Toasts.


So to answer your question, you need to:

1) At the start of your test method, replace the AuthAPI's loginService object with your mockApi object, then call AuthAPI.Login().

2) Use verify() as you already are to check that the function has been called.

3) Create a sample AuthObject and pass it to the cb.getValue().success() function.

4) Obtain the AuthObject from your Bus and assert that it is the same one you sent to the callback.success() function.

This tests that your AuthAPI.Login() correctly sends to your Bus the AuthObject that it would retrieve from Retrofit.


(I realise the SO question was written some time ago, but as I came across the same article and had the same confusion very recently, I thought this answer could be useful for others.)

Solution 2

The problem is that you call verify at the wrong moment: the purpose of verify is to verify that the interactions with mockApi were what you expected. So normally you would see something like:

authApi.login();
Mockito.verify(mockApi).basicLogin((cb.capture()));

That's also what the error message is telling you: verify expected basicLogin to be called but it wasn't.

I've read that article too and felt there was something missing. I don't actually undestand argument capture yet. So can't help you with that :)

Share:
12,528

Related videos on Youtube

Admin
Author by

Admin

Updated on June 06, 2022

Comments

  • Admin
    Admin almost 2 years

    I separated retrofit api calls methods from the activity code and I want to do a unit test on these methods, one example: The interface:

    public interface LoginService {
        @GET("/auth")
        public void basicLogin(Callback<AuthObject> response);
    }
    

    and this is the method that do the call, in the main activity I get the object by the event bus.

    public class AuthAPI {
        private Bus bus;
        LoginService loginService;
    
        public AuthAPI(String username, String password) {
            this.bus = BusProvider.getInstance().getBus();
            loginService = ServiceGenerator.createService(LoginService.class,
                    CommonUtils.BASE_URL,
                    username,
                    password);
        }
    
        public void Login() {
    
            loginService.basicLogin(new Callback<AuthObject>() {
                @Override
                public void success(AuthObject authObject, Response response) {
                    bus.post(authObject);
                }
    
                @Override
                public void failure(RetrofitError error) {
                    AuthObject authObject = new AuthObject();
                    authObject.setError(true);
                    bus.post(authObject);
                }
            });
        }
    
    }
    

    And here the test

    @RunWith(MockitoJUnitRunner.class)
    public class AuthCallTest extends TestCase {
    
        AuthAPI authAPI;
    
        @Mock
        private LoginService mockApi;
    
        @Captor
        private ArgumentCaptor<Callback<AuthObject>> cb;
    
        @Before
        public void setUp() throws Exception {
            authAPI = new AuthAPI("username", "password");
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testLogin() throws Exception {
    
            Mockito.verify(mockApi).basicLogin((cb.capture()));
    
            AuthObject authObject = new AuthObject();
            cb.getValue().success(authObject, null);
    
            assertEquals(authObject.isError(), false);
        }
    }
    

    when I launch the test I have this error

    Wanted but not invoked:
    mockApi.basicLogin(<Capturing argument>);
    -> at AuthCallTest.testLogin(AuthCallTest.java:42)
    Actually, there were zero interactions with this mock.
    

    What I did wrong, this is driving me crazy I tried to follow this guide without success: http://www.mdswanson.com/blog/2013/12/16/reliable-android-http-testing-with-retrofit-and-mockito.html

    someone help me :(

  • IgorGanapolsky
    IgorGanapolsky over 8 years
    It seems like you want to instrument your tests with Android activity context. I don't think this is the purest approach to testing REST APIs - which should be tested on the JVM (junit).
  • Steve Haley
    Steve Haley over 8 years
    @IgorGanapolsky You can't directly test REST APIs in an Android app in JUnit if you use any class that references the Android SDK, as JUnit doesn't load the Android classes. However, that's the purpose of the article above - how to use Robolectric and Mockito to perform local tests of REST APIs without making actual server calls, while still accessing real Android classes.
  • IgorGanapolsky
    IgorGanapolsky over 8 years
    @SteveHaley The problem with the article above is that it is two years old. That is ancient in Android dev sense. That code is hardly usable at this point.
  • Steve Haley
    Steve Haley over 8 years
    @IgorGanapolsky I viewed it more as an example/proof of concept than "this is the right way to do things". If you have a link to something newer, feel free to post it.
  • IgorGanapolsky
    IgorGanapolsky over 8 years
    @SteveHaley What good is a proof of concept if the code doesn't compile? Android Studio doesn't let you run proof of concepts.
  • Steve Haley
    Steve Haley over 8 years
    @IgorGanapolsky Not being able to compile his code is an entirely different issue to whether Mockito paired with Robolectric is a good system to test Retrofit APIs. That's going back to his article being very unclear. With a bit of work to fill in the gaps, you can create working tests using the system he describes. As to whether Mockito+Robolectric is still the best way to do it, I couldn't say as I'm not familiar enough with the alternatives.