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("[email protected]"));
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)
You鈥檒l 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 MockWebServerand 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.
Most helpful comment
If anyone else stumbles on this problem here is the solution. Modify your custom AndroidJUnitRunner like so:
copied from this article.