Android Unit Test with Retrofit and Mockito
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 :)
Related videos on Youtube
Admin
Updated on June 06, 2022Comments
-
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 over 8 yearsIt 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 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 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 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 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 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.