Quentin Klein
Quentin Klein

Reputation: 1393

Robolectric + Retrofit + Unit test

I have a project that maps an API using Retrofit 2.0.2.

It works fine, so I decided to write some functional tests to make sure it's ok in the future. For multiple reasons, I want them to run out of any Android device or emulator.

The thing is, I use some android classes (like Base64) so I needed an env, that's why I decided to use Robolectric 3.0.

In order to fake the responses of my API (pure unit test) I use an OkHttp Interceptor as explained almost everywhere.

Here is the problem, when I run my tests from Android Studio (2.x) everything works fine. But when I run them from a Gradle command line, it seems the Interceptor doesn't intercept anything and I get a real response from my API.

Here is the code of the Unit test:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
public class TestService {

    private API api;

    @Rule
    public ErrorCollector collector = new ErrorCollector();

    @Before
    public void setUp() {
        // Initialize the API with fake data
        api = API.with("fake_client", "fake_secret", "fake_redirect");
    }

    @Test
    public void refreshToken() {
        Response<Token> tokenResponse = api.refreshToken("fake_refresh");
        collector.checkThat("no error", tokenResponse.error, nullValue());
        collector.checkThat("not null", tokenResponse.item, not(nullValue()));

        collector.checkThat("access token", tokenResponse.item.getAccessToken(), is("22fe0c13e995da4a44a63a7ff549badb5d337a42bf80f17424482e35d4cca91a"));
        collector.checkThat("expires at", tokenResponse.item.getExpiresAt(), is(1382962374L));
        collector.checkThat("expires in", tokenResponse.item.getExpiresIn(), is(3600L));
        collector.checkThat("refresh token", tokenResponse.item.getRefreshToken(), is("8eb667707535655f2d9e14fc6491a59f6e06f2e73170761259907d8de186b6a1"));
    }
}

The code of the API init (called from with):

private API(@NonNull final String clientId, @NonNull final String clientSecret, @NonNull final String redirectUri) {
    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new Interceptor() {
                @Override
                public okhttp3.Response intercept(Chain chain) throws IOException {
                    Request original = chain.request();
                    Request.Builder requestBuilder = original.newBuilder()
                            .header("Authorization", "Basic " + Base64.encodeToString((clientId + ":" + clientSecret).getBytes(), Base64.NO_WRAP).replace("\n", ""));
                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            })
            .addInterceptor(new APIInterceptor())
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Important point
            .build();
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ROUTE_BASE)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
            .build();

    authService = retrofit.create(AuthService.class);
}

And finally the APIInterceptor:

public class APIInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        ResponseBody response = ResponseBody.create(MediaType.parse("application/json"), response);
        return new Response.Builder()
            .request(request)
            .addHeader("Content-Type", "application/json")
            .protocol(Protocol.HTTP_1_1)
            .code(200)
            .body(response)
            .build();
    }
}

In my test but it didn't work. How can I fix this?

Edit

I have 2 flavors; a flavor "production" where the class APIInterceptor does nothing. And a flavor "sandbox" where the class APIInterceptor does the mock.

So when I run the tests from command line I use gradlew :module:testSandboxDebugUnitTest

Then I tried something interesting. If I take the content of the APIInterceptor class (the "sandbox" one) and then copy the implementation directly in the addInterceptor so it looks like

The code of the API init (called from with):

private API(@NonNull final String clientId, @NonNull final String clientSecret, @NonNull final String redirectUri) {
    OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new Interceptor() {
                @Override
                public okhttp3.Response intercept(Chain chain) throws IOException {
                    Request original = chain.request();
                    Request.Builder requestBuilder = original.newBuilder()
                            .header("Authorization", "Basic " + Base64.encodeToString((clientId + ":" + clientSecret).getBytes(), Base64.NO_WRAP).replace("\n", ""));
                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            })
            .addInterceptor(new Interceptor() {
                // The content of APIInterceptor here
            })
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Important point
            .build();
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(ROUTE_BASE)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
            .build();

    authService = retrofit.create(AuthService.class);
}

This works fine!

So the issue seems to be that Robolectric does not use the implementations in flavors, it seems it just ignores everything in flavors.

Upvotes: 2

Views: 1946

Answers (1)

Quentin Klein
Quentin Klein

Reputation: 1393

Ok, thanks to @EugenMartynov I found the answer.

In fact I was using another module and this module has always an interceptor for tests, but it was the same class name in the same package name.

So when gradle was compiling, it was using the object from the other package in release mode which does the network operations without intercepting anything !

The solution was to rename the Interceptor class !

Upvotes: 2

Related Questions