Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NetworkOnMainThreadException #3184

Closed
rusmichal opened this issue Feb 22, 2017 · 10 comments
Closed

NetworkOnMainThreadException #3184

rusmichal opened this issue Feb 22, 2017 · 10 comments

Comments

@rusmichal
Copy link

Hey. I know that there already is lot of similar issues but I cannot find solution.
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("developer@appppp.com"));
        onView(withId(R.id.pass_input)).perform(clearText(), typeText("qqqqqqqq"));
        onView(withId(R.id.login_button)).perform(click());

        solo.waitForDialogToOpen();
    }
}

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:

public void login(UserLoginModel userLoginModel, final Account.Login callback) {
        final CountDownLatch latch = new CountDownLatch(1);

        mockWebServer.enqueue(MockResponse.getMockResponse(200, MockResponse.getResourceAsString(this, "login.json")));

        super.login(userLoginModel, new Account.Login() {
            @Override
            public void onLoginSuccess(LoginResponse response) {
                callback.onLoginSuccess(response);

                latch.countDown();
            }

            @Override
            public void onLoginFail(String message) {

            }
        });

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

When I want to run my tests I got:

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)
@rusmichal rusmichal changed the title NetworkOnMainThreadException again NetworkOnMainThreadException Feb 22, 2017
@swankjesse
Copy link
Collaborator

You’ll need to avoid starting your MockWebServer on the main thread.

@rusmichal
Copy link
Author

But how?

@DavidEdwards
Copy link

@rusmichal

If you are confused about threading, you should take a look at this:

http://stackoverflow.com/questions/6343166/how-to-fix-android-os-networkonmainthreadexception

There are some good explanations, and some solutions.

@rusmichal
Copy link
Author

I guess you don't understand me. I know that about NetworkOnMainThreadException and why it is appearing in Android OS. But in my test in line .baseUrl(mockWebServer.url("/")) throw NetworkOnMainThreadException. I don't understand why in this line?

Maybe I will explain more what I want get. I have finished my app, couple of activties and fragments. I have class BasicFragment where I inject ApiClient object to request with backend. For example let get LoginFragment. There is simple form fields email and password and login button. In my UI tests I type email and password and all looks like real user types. I added performClick for login button. So It start calls real backend. This I want change to call mock server and emit mock response from json file.

This is weird because I have Unit test where I test backend stuff and I use MockWebserver and there it works fine.

My first post is instrumental test but below is my unit test and it works:

public class TestApiModule extends ApiModule {
    private Context context;
    private DataManager dataManager;
    private String baseUrl;

    public TestApiModule(String baseUrl, Context context, DataManager dataManager) {
        this.baseUrl = baseUrl;
        this.context = context;
        this.dataManager = dataManager;
    }

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

    @Override
    public Retrofit provideRetrofit(OkHttpClient okHttpClient) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .client(okHttpClient)
                .build();
        return retrofit;
    }

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

    public ApiClient provideApiManager() {
        OkHttpClient okHttpClient = provideOkHttpClient(dataManager);
        Retrofit retrofit = provideRetrofit(okHttpClient);
        ApiService apiService = provideApiService(retrofit);

        return new ApiClient(context, apiService, dataManager);
    }
}
public class BaseApiModuleTest {

    @Mock
    protected Context context;

    protected DataManager dataManager;
    protected MockWebServer mockWebServer;
    protected ApiClient apiClient;

    @Before
    public void setup() throws Exception {
        MockitoAnnotations.initMocks(this);
        mockWebServer = new MockWebServer();
        dataManager = new DataManager(context);
        apiClient = new TestApiModule(
                mockWebServer.url("/").toString(),
                context,
                dataManager
        ).provideApiManager();
    }

    @Test
    public void testSetupIsCorrect() throws Exception {
        assertNotNull(dataManager);
        assertNotNull(context);
        assertNotNull(mockWebServer);
        assertNotNull(apiClient);
    }

}

@DavidEdwards
Copy link

DavidEdwards commented Feb 23, 2017

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 am not very familiar with Dagger. However, to me it looks like this method is creating MockWebServer and Dagger is starting the mockWebServer? If that is the case, you should check whether this thread is running on the Main Thread.

You can output to logcat to determine if this is where the issue is. In these two places add the following code.

  • initComponent() method, at the start.
  • provideRetrofit(OkHttpClient okHttpClient) method, at the start.

Log.w("MAIN_THREAD_TEST", "On main thread="+Looper.getMainLooper().equals(Looper.myLooper()));

@rusmichal
Copy link
Author

Class MyApp extends MultidexApplication where I initialize all components needed in app (Dagger). MockQrApplication extends MyApp and override initComponent for initialization mock modules for tests. So I don't have to change my production code only I provide mock modules.

Both logs are:

W/MAIN_THREAD_TEST: On main thread=true
W/MAIN_THREAD_TEST: On main thread=true

@DavidEdwards
Copy link

Since both logs output true, it seems clear to me that you are creating your mockWebServer on the main thread. So my first step would be to push that onto a background thread.

I am not familiar with the lifecycles of the classes you are using. However, you could attempt to initialize / start the mockWebServer in its own thread.

One example:

new Thread(new Runnable() {
	@Override
	public void run() {
		mockWebServer = new MockWebServer();
		mockWebServer.start();
	}
});

@rusmichal
Copy link
Author

It doesn't work because mockWebServer is null in MainThread.

I tried this

public class MockQrApplication extends QrContacts {
    private MockWebServer mockWebServer;

    protected void initComponent() {

        MyThread thread = new MyThread();
        thread.start();
        mockWebServer = thread.mockWebServerInThread;

        component = DaggerQrContactsAppComponent
                .builder()
                .qrContactsAppModule(new QrContactsAppModule(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);
    }

    class MyThread extends Thread {

        public MockWebServer mockWebServerInThread;

        @Override
        public void run() {
            mockWebServerInThread = new MockWebServer();
            try {
                mockWebServerInThread.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

In your solution and my (above) I got this:

java.lang.NullPointerException: Attempt to invoke virtual method 'okhttp3.HttpUrl okhttp3.mockwebserver.MockWebServer.url(java.lang.String)' on a null object reference
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)

@swankjesse
Copy link
Collaborator

No action for us to take on this.

@RafalManka
Copy link

RafalManka commented Feb 11, 2019

If anyone else stumbles on this problem here is the solution. Modify your custom AndroidJUnitRunner like so:

class EspressoRunner : AndroidJUnitRunner() {

    override fun onCreate(arguments: Bundle) {
        StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().permitAll().build())
        super.onCreate(arguments)
    }

    ...

}

copied from this article.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants