Android Unit Test with Retrofit2 and Mockito or Robolectric

26,979

It is generally not a good idea to test real server requests. See this blog post for an interesting discussion on the topic. According to the author, using your real server is a problem because:

  • Another moving piece that can intermittently fail
  • Requires some expertise outside of the Android domain to deploy the server and keep it updated
  • Difficult to trigger error/edge cases
  • Slow test execution (still making HTTP calls)

You can avoid all the issues above by using a mock server such as OkHttp's MockWebServer to simulate real response results. For example:

@Test
public void test() throws IOException {
    MockWebServer mockWebServer = new MockWebServer();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(mockWebServer.url("").toString())
            //TODO Add your Retrofit parameters here
            .build();

    //Set a response for retrofit to handle. You can copy a sample
    //response from your server to simulate a correct result or an error.
    //MockResponse can also be customized with different parameters
    //to match your test needs
    mockWebServer.enqueue(new MockResponse().setBody("your json body"));

    YourRetrofitService service = retrofit.create(YourRetrofitService.class);

    //With your service created you can now call its method that should 
    //consume the MockResponse above. You can then use the desired
    //assertion to check if the result is as expected. For example:
    Call<YourObject> call = service.getYourObject();
    assertTrue(call.execute() != null);

    //Finish web server
    mockWebServer.shutdown();
}

If you need to simulate network delays, you can customize your response as follows:

MockResponse response = new MockResponse()
    .addHeader("Content-Type", "application/json; charset=utf-8")
    .addHeader("Cache-Control", "no-cache")
    .setBody("{}");
response.throttleBody(1024, 1, TimeUnit.SECONDS);

Alternatively, you can use MockRetrofit and NetworkBehavior to simulate API responses. See here an example of how to use it.

Finally, if you just want to test your Retrofit Service, the easiest would be to create a mock version of it that emits mock results for your tests. For example, if you have the following GitHub service interface:

public interface GitHub {
    @GET("/repos/{owner}/{repo}/contributors")
    Call<List<Contributor>> contributors(
        @Path("owner") String owner,
        @Path("repo") String repo);
}

You can then create the following MockGitHub for your tests:

public class MockGitHub implements GitHub {
    private final BehaviorDelegate<GitHub> delegate;
    private final Map<String, Map<String, List<Contributor>>> ownerRepoContributors;

    public MockGitHub(BehaviorDelegate<GitHub> delegate) {
        this.delegate = delegate;
        ownerRepoContributors = new LinkedHashMap<>();

        // Seed some mock data.
        addContributor("square", "retrofit", "John Doe", 12);
        addContributor("square", "retrofit", "Bob Smith", 2);
        addContributor("square", "retrofit", "Big Bird", 40);
        addContributor("square", "picasso", "Proposition Joe", 39);
        addContributor("square", "picasso", "Keiser Soze", 152);
    }

    @Override public Call<List<Contributor>> contributors(String owner, String repo) {
        List<Contributor> response = Collections.emptyList();
        Map<String, List<Contributor>> repoContributors = ownerRepoContributors.get(owner);
        if (repoContributors != null) {
            List<Contributor> contributors = repoContributors.get(repo);
            if (contributors != null) {
                response = contributors;
            }
        }
        return delegate.returningResponse(response).contributors(owner, repo);
    }
}

You can then use the MockGitHub on your tests to simulate the kinds of responses you are looking for. For the full example, see the implementations of the SimpleService and SimpleMockService for this Retrofit example.

Having said all this, if you absolutely must connect to the actual server, you can set Retrofit to work synchronously with a custom ImmediateExecutor:

public class ImmediateExecutor implements Executor {
    @Override public void execute(Runnable command) {
        command.run();
    }
}

Then apply it to the OkHttpClient you use when building the Retrofit:

OkHttpClient client = OkHttpClient.Builder()
        .dispatcher(new Dispatcher(new ImmediateExecutor()))
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(client)
        //Your params
        .build();
Share:
26,979
AndrewS
Author by

AndrewS

Android Developer

Updated on July 19, 2022

Comments

  • AndrewS
    AndrewS almost 2 years

    Can I test real response from retrofit2beta4? Do i need Mockito or Robolectic?

    I don't have activities in my project, it will be a library and I need to test is server responding correctly. Now I have such code and stuck...

    @Mock
    ApiManager apiManager;
    
    @Captor
    private ArgumentCaptor<ApiCallback<Void>> cb;
    
    @Before
    public void setUp() throws Exception {
        apiManager = ApiManager.getInstance();
        MockitoAnnotations.initMocks(this);
    }
    
    @Test
    public void test_login() {
        Mockito.verify(apiManager)
               .loginUser(Mockito.eq(login), Mockito.eq(pass), cb.capture());
        // cb.getValue();
        // assertEquals(cb.getValue().isError(), false);
    }
    

    I can make fake response, but I need to test real. Is it success? Is it's body correct? Can you help me with code?

  • AndrewS
    AndrewS over 8 years
    All your answer is based on "simulation". As i said on question topic - i can do that. My project is a library that will work with server API. The only thing i need to test - is changing on server, i need to test real responses.
  • Ricardo
    Ricardo over 8 years
    I offered alternatives because I think it doesn't make sense to test on a real server. You can't be sure that the test will work in different locations for different users, you can't easily test connections problems, and so on. The server is not something that belongs to your library and should not be treated as such, I think. That's why it is generally better to test server responses instead. If you use a MockWebServer you can have your tests run as if connected to the real server. Your library wouldn't know the difference.
  • AndrewS
    AndrewS over 8 years
    If i make fake success response - i will get success test. What is the point of this test? I just need to know when server response changed (by tests), to update my library for new response. I will never know that something changed if i make fake response.
  • Ricardo
    Ricardo over 8 years
    That's why you need to make success responses and bad responses as well. And test how your library reacts to the different kinds of responses a server can give to your library. With a MockWebServer you can and should test as many different types of responses as possible.
  • AndrewS
    AndrewS over 8 years
    My library has no reaction and no uses of response. It just get data from REST and parse it to Model. I need to test that server is not changing models. I don't need to test my code. How can i test that? is was my question.
  • Ricardo
    Ricardo over 8 years
    I edited my answer with a way to use OkHttpClient and Retrofit to connect to the actual server synchronously so you can use them on your robolectric tests.
  • Alberto S.
    Alberto S. over 8 years
    If you need to test that the server is not changing the models then what you should test is the server itself. Your api tests should only depend on your actual api code and mock all the server responses (both correct and incorrect ones)
  • AndrewS
    AndrewS over 8 years
    @Dr.Pelocho server is not mine, but my project is based on that server. That's why i need to be sure that nothing changed there.
  • Milan
    Milan over 8 years
    @Ricardo retrofit-mock:2.0.0-beta2 does not have BehaviorDelegate, it exist in the commit logs. Which retoofit mock version are you using?
  • Ricardo
    Ricardo over 8 years
    I'm not currently using it like the example above. My versions of GitHubMock simply return the objects I want to test. In any case, BehaviorDelegate is not yet available on Maven. You have to clone the master repository if you want to use it
  • Mike6679
    Mike6679 over 7 years
    for me: new Retrofit.Builder() ... .build() throws a java.lang.reflect.InvocationTargetException. Anyone know why that would be?
  • Oliver Dixon
    Oliver Dixon over 7 years
    What's the point of endpoint testing if you're not testing against your production environments. If that part fails then good! You can tell your darn API team! Otherwise you are just testing Retrofit.. Pointless.
  • Anuj Garg
    Anuj Garg over 4 years
    Whoever here are supporting to test with real endpoints are forgetting that this is 'Unit testing' and you are expected to test only one part of the whole application. No code can work if it's dependencies are failing, but in unit testing, you need to assume all possible behaviors of dependencies (modules\classes\endpoints\library) and write testcases just to test your peice of code.