Search code examples
androidandroid-espressorobotiumui-testingandroid-instrumentation

Test running failed: Instrumentation run failed due to 'android.os.NetworkOnMainThreadException'


I've started working with Espresso UI tests. I prepare custom MockTestRunner, MockApplication for initialization Dagger components and I've defined mock modules too. It looks like that:

public class MockTestRunner extends AndroidJUnitRunner {
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return super.newApplication(cl, MockMyApplication.class.getName(), context);
    } }

MyApp is extended by

public class MockQrApplication extends MyApp {
    private MockWebServer mockWebServer;

    protected void initComponent() {
        mockWebServer = new MockWebServer();

        component = DaggerMyAppComponent
                .builder()
                .myAppModule(new MyAppModule(this))
                .busModule(new BusModule())
                .apiModule(new MockApiModule(mockWebServer))
                .facebookModule(new FacebookModule())
                .dataManagerModule(new DataManagerModule())
                .greenDaoModule(new GreenDaoModule())
                .trackModule(new TrackModule(this))
                .build();

        component.inject(this);
    }
}

I added testInstrumentationRunner into gradle

defaultConfig {
        ....
        multiDexEnabled true

        testInstrumentationRunner "a.b.c.MockTestRunner"
    }

I want run login tests in my LoginActivity

@RunWith(AndroidJUnit4.class)
@LargeTest
public class LoginActivityTest {
    protected Solo solo;

    @Rule
    public ActivityTestRule<LoginActivity> activityTestRule = new ActivityTestRule(LoginActivity.class);

    @Before
    public void setUp() throws Exception {
        initVariables();
    }

    protected void initVariables() {
        solo = new Solo(InstrumentationRegistry.getInstrumentation(), activityTestRule.getActivity());
    }

    @Test
    public void testLayout() {
        solo.waitForFragmentByTag(LoginFragment.TAG, 1000);

        onView(withId(R.id.email_input)).perform(clearText(), typeText("[email protected]"));
        onView(withId(R.id.pass_input)).perform(clearText(), typeText("qqqqqqqq"));
        onView(withId(R.id.login_button)).perform(click());

        solo.waitForDialogToOpen();
    }
}

When I want to run my tests I got:

Client not ready yet..
Started running tests
Test running failed: Instrumentation run failed due to 'android.os.NetworkOnMainThreadException'
Empty test suite.

[Edit]

This is MockApiModule which extends ApiModule class

public class MockApiModule extends ApiModule {
    private MockWebServer mockWebServer;

    public MockApiModule(MockWebServer mockWebServer) {
        this.mockWebServer = mockWebServer;
    }

    @Override
    public OkHttpClient provideOkHttpClient(DataManager dataManager) {
        return new OkHttpClient.Builder()
                .build();
    }

    @Override
    public Retrofit provideRetrofit(OkHttpClient okHttpClient) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(mockWebServer.url("/"))       // throw NetworkOnMainThreadException
                .addConverterFactory(NullOnEmptyConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(okHttpClient)
                .build();
        return retrofit;
    }

    @Override
    public ApiService provideApiService(Retrofit retrofit) {
        return retrofit.create(ApiService.class);
    }

    @Override
    public ApiClient provideApiManager(Application application, ApiService apiService, DataManager dataManager) {
        return new MockApiClient(application, apiService, dataManager, mockWebServer);
    }
}

API login request looks like that:

apiService.postLoginUser(userLoginModel).enqueue(new Callback<JsonObject>() {
            @Override
            public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
                if (response.isSuccess()) {
                    LoginResponse loginResponse = null;
                    try {
                        loginResponse = gson.fromJson(MockResponse.getResourceAsString(this, "login.json"), LoginResponse.class);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    callback.onLoginSuccess(loginResponse);
                } else {
                    callback.onLoginFail(response.errorBody().toString());
                }
            }

            @Override
            public void onFailure(Call<JsonObject> call, Throwable t) {
                callback.onLoginFail(t.getMessage());
            }
        });

It works if I change MockMyApplication into MyApp class of application in MockTestRunner

I get stracktrace

android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1285)
at java.net.InetAddress.lookupHostByName(InetAddress.java:431)
at java.net.InetAddress.getAllByNameImpl(InetAddress.java:252)
at java.net.InetAddress.getByName(InetAddress.java:305)
at okhttp3.mockwebserver.MockWebServer.start(MockWebServer.java:303)
at okhttp3.mockwebserver.MockWebServer.start(MockWebServer.java:293)
at okhttp3.mockwebserver.MockWebServer.maybeStart(MockWebServer.java:143)
at okhttp3.mockwebserver.MockWebServer.getHostName(MockWebServer.java:172)
at okhttp3.mockwebserver.MockWebServer.url(MockWebServer.java:198)
at com.mooduplabs.qrcontacts.modules.MockApiModule.provideRetrofit(MockApiModule.java:38)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideRetrofitFactory.get(ApiModule_ProvideRetrofitFactory.java:23)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideRetrofitFactory.get(ApiModule_ProvideRetrofitFactory.java:9)
at dagger.internal.ScopedProvider.get(ScopedProvider.java:46)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiServiceFactory.get(ApiModule_ProvideApiServiceFactory.java:23)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiServiceFactory.get(ApiModule_ProvideApiServiceFactory.java:9)
at dagger.internal.ScopedProvider.get(ScopedProvider.java:46)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiManagerFactory.get(ApiModule_ProvideApiManagerFactory.java:31)
at com.mooduplabs.qrcontacts.modules.ApiModule_ProvideApiManagerFactory.get(ApiModule_ProvideApiManagerFactory.java:11)
at dagger.internal.ScopedProvider.get(ScopedProvider.java:46)
at com.mooduplabs.qrcontacts.activities.BaseActivity_MembersInjector.injectMembers(BaseActivity_MembersInjector.java:44)
at com.mooduplabs.qrcontacts.activities.BaseActivity_MembersInjector.injectMembers(BaseActivity_MembersInjector.java:13)
at com.mooduplabs.qrcontacts.components.DaggerQrContactsAppComponent.inject(DaggerQrContactsAppComponent.java:91)
at com.mooduplabs.qrcontacts.activities.BaseActivity.init(BaseActivity.java:74)
at com.mooduplabs.qrcontacts.activities.BaseActivity.onCreate(BaseActivity.java:64)
at android.app.Activity.performCreate(Activity.java:6367)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1110)
at android.support.test.runner.MonitoringInstrumentation.callActivityOnCreate(MonitoringInstrumentation.java:532)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2404)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2511)
at android.app.ActivityThread.access$900(ActivityThread.java:165)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1375)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:5621)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)

Solution

  • Ran into similar issue, just avoid starting your activity right away during testing. You can achieve this by adjusting your test rule like the snippet below,

    @Rule
    // Do not launch the activity right away
    public ActivityTestRule<LoginActivity> activityTestRule = new ActivityTestRule(LoginActivity.class, true, false);
    

    and then starting the mock web server by yourself during test setup. It is basically happening because mockWebServer.url("/") tries to start the mock server for you in case it has not already started. You need to do it first if you don't want to do it later on during test execution (NetworkOnMainThreadException case).