Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using MockWebServer with Robolectric

I'm trying to unit test some API calls using MockWebServer and Robolectric.

My test class is annotated with:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)

However when trying to build the Retrofit instance I get the following exception:

java.lang.NullPointerException
    at android.os.Handler.__constructor__(Handler.java:229)
    at android.os.Handler.<init>(Handler.java)
    at retrofit2.Platform$Android$MainThreadExecutor.<init>(Platform.java:105)
    at retrofit2.Platform$Android.defaultCallbackExecutor(Platform.java:97)
    at retrofit2.Retrofit$Builder.build(Retrofit.java:556)

The code I'm using to build the retrofit instance:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(mMockServer.url(""))
                .addConverterFactory(GsonConverterFactory.create())
                .build();

The exception above is returned upon calling .build().

How do I fix this problem?

like image 794
user_4685247 Avatar asked Jun 27 '16 07:06

user_4685247


3 Answers

I had this same problem. There is a bug open about the underlying cause here but while that is ongoing, I settled for using Robolectric 3.0 and the solution outlined by dave-r12 here which is to create the mock I've included below.

@RunWith(RobolectricGradleTestRunner.class)
@Config(shadows = CreateOkHttpClientTest.MyNetworkSecurityPolicy.class)
public class CreateOkHttpClientTest {

    @Test
    ...

    @Implements(NetworkSecurityPolicy.class)
    public static class MyNetworkSecurityPolicy {

        @Implementation 
        public static NetworkSecurityPolicy getInstance() {
            try {
                Class<?> shadow = MyNetworkSecurityPolicy.class.forName("android.security.NetworkSecurityPolicy");
                return (NetworkSecurityPolicy) shadow.newInstance();
            } catch (Exception e) {
                throw new AssertionError();
            }
        }

        @Implementation 
        public boolean isCleartextTrafficPermitted() {
            return true;
        }
    }

}
like image 140
Nick Cardoso Avatar answered Nov 02 '22 16:11

Nick Cardoso


An alternate solution that seems to work is to just provide a dummy executor when setting up Retrofit

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://www.example.com/")
            .client(client)
            .callbackExecutor(new Executor() {
                @Override
                public void execute(Runnable command) {
                    //doesn't matter since tests are synchronous
                }
            })
            .addConverterFactory(ScalarsConverterFactory.create())
            .build();

It may be something that in the end needs to be corrected on the Retrofit side, somehow detecting if the current Platform is Robolectric and return a dummy executor or something.

like image 1
Jawnnypoo Avatar answered Nov 02 '22 16:11

Jawnnypoo


If you are stuck on Robolectric 3 or below and you are targeting the API 25 the proper solution is almost the same like accepted one.

@Implements(NetworkSecurityPolicy.class)
public class NetworkSecurityPolicyShadow {

    @Implementation
    public static NetworkSecurityPolicy getInstance() {
        try {
            Class<?> shadow = Class.forName("android.security.NetworkSecurityPolicy");
            return (NetworkSecurityPolicy) shadow.newInstance();
        } catch (Exception e) {
            throw new AssertionError();
        }
    }

    @Implementation
    public boolean isCleartextTrafficPermitted(String host) {
        return true;
    }
}

Only difference is in isCleartextTrafficPermitted(String host) because the OkHttp will try to call this method with host argument.

like image 1
jakubbialkowski Avatar answered Nov 02 '22 16:11

jakubbialkowski