Closed rusmichal closed 7 years ago
You’ll need to avoid starting your MockWebServer on the main thread.
But how?
@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.
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);
}
}
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.
Log.w("MAIN_THREAD_TEST", "On main thread="+Looper.getMainLooper().equals(Looper.myLooper()));
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
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();
}
});
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)
No action for us to take on this.
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.
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 customMockTestRunner
,MockApplication
for initializationDagger
components and I've defined mock modules too. It looks like that:MyApp
is extended byI added
testInstrumentationRunner
intogradle
I want run login tests in my
LoginActivity
This is
MockApiModule
which extendsApiModule
classAPI login request looks like that:
It works if I change
MockMyApplication
intoMyApp
class of application inMockTestRunner
When I want to run my tests I got: