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();
}
}
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)
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).
Related
when i try to test my application on more android device, i get this error:
java.lang.NoClassDefFoundError: com.myapp.androidapp.Dagger.Modules.NetworkModule_LoggingInterceptorFactory
i search more problems about Dagger, but i can't find whats this error and how can i resolve that,
my application work fine on Sony and Lenovo tables , but it doesn't work on Samsung
Application class:
component = DaggerGithubApplicationComponent.builder()
.contextModule(new ContextModule(this))
.jobManagerModule(new JobManagerModule())
.networkServiceModule(new NetworkServiceModule("https://api.github.com/"))
.build();
githubService = component.getGithubService();
picasso = component.getPicasso();
jobManager = component.getJobManager();
handler = component.getHandler();
my NetworkModule class:
#Module(includes = ContextModule.class)
public class NetworkModule {
#Provides
#AlachiqApplicationScope
public HttpLoggingInterceptor loggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
#Override
public void log(String message) {
Timber.e(message);
}
});
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
return interceptor;
}
#Provides
#AlachiqApplicationScope
public RxJavaCallAdapterFactory rxAdapter() {
return RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io());
}
#Provides
#AlachiqApplicationScope
public Cache cache(File cacheFile) {
return new Cache(cacheFile, 10 * 1000 * 1000); //10MB Cahe
}
#Provides
#AlachiqApplicationScope
public File cacheFile(#ApplicationContext Context context) {
return new File(context.getCacheDir(), "okhttp_cache");
}
#Provides
#AlachiqApplicationScope
public OkHttpClient okHttpClient(HttpLoggingInterceptor loggingInterceptor, Cache cache) {
return new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.cache(cache)
.build();
}
}
GithubApplicationComponent:
#MyAppApplicationScope
#Component(
modules = {
UserInformationModule.class,
NetworkServiceModule.class,
PicassoModule.class,
JobManagerModule.class,
SocketModule.class,
HandlerModule.class,
ActivityModule.class
}
)
public interface GithubApplicationComponent {
GithubService getGithubService();
JobManager getJobManager();
Picasso getPicasso();
Socket getSocket();
Handler getHandler();
}
My full LogCat:
java.lang.NoClassDefFoundError: com.myapp.androidapp.Dagger.Modules.NetworkModule_LoggingInterceptorFactory
at com.myapp.androidapp.Dagger.Components.DaggerGithubApplicationComponent.initialize(DaggerGithubApplicationComponent.java:87)
at com.myapp.androidapp.Dagger.Components.DaggerGithubApplicationComponent.<init>(DaggerGithubApplicationComponent.java:76)
at com.myapp.androidapp.Dagger.Components.DaggerGithubApplicationComponent.<init>(DaggerGithubApplicationComponent.java:0)
at com.myapp.androidapp.Dagger.Components.DaggerGithubApplicationComponent$Builder.build(DaggerGithubApplicationComponent.java:210)
at com.myapp.androidapp.Alachiq.onCreate(Alachiq.java:124)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1017)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4717)
at android.app.ActivityThread.access$1600(ActivityThread.java:163)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1419)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5536)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1397)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1192)
I have MVP in my application. Presenter has interface
public interface ILoginPresenter<V> extends Presenter<V> {
void logUserIn(String email, String password, String deviceToken, String deviceType);
}
Realization has RX Single
mLoginSubscription = mModel.logUserIn(email, password, deviceToken, deviceType)
.compose(RxUtil.setupNetworkSingle())
.subscribe(new Subscriber<User>() {
#Override
public void onCompleted() {
Timber.i("Log in complete");
}
#Override
public void onError(Throwable e) {
Timber.e(e, "Retrofit could not get User.");
getView().dismissProgressDialog();
}
#Override
public void onNext(UserResponseRetrofit response) {
Timber.i("Retrofit is attempting to get User");
mSaveModel.saveUser(user);
getView().dismissProgressDialog();
getView().goToMenuActivity();
}
});
Also i have module for Dagger
#Module
public class ModelModule {
#Provides
#ScreenScope
public ILoginModel provideLoginModel(LoginModel p) {
return p;
}
}
My unit test look like next:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml")
public class LoginPresenterTest {
public static final String SOME_OTHER_TOKEN = "someOtherToken";
private AppComponent mAppComponent;
private LoginComponent mLoginComponent;
private ILoginView mockView;
private ModelModule mockModel;
private ILoginPresenter mLoginPresenter;
#Before
public void setup() {
// Creating the mocks
mockView = Mockito.mock(ILoginView.class);
mockModel = Mockito.mock(ModelModule.class);
ILoginModel mock = Mockito.mock(ILoginModel.class);
User urr = Mockito.mock(User.class);
Mockito.when(mockModel.provideLoginModel(null)).thenReturn(mock);
Mockito.when(mock.logUserIn("", "", "", "")).thenReturn(ScalarSynchronousSingle.just(urr));
mAppComponent = DaggerAppComponent.builder()
.appModule(new AppModule(RuntimeEnvironment.application))
.build();
mLoginComponent = DaggerLoginComponent.builder()
.appComponent(mAppComponent)
.modelModule(mockModel)
.presenterModule(new PresenterModule())
.build();
mLoginPresenter = mLoginComponent.provideLoginPresenter();
mLoginPresenter.setView(mockView);
}
#Test
public void testLogin() {
mLoginPresenter.logUserIn("", "", "", "");
try {
java.lang.Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Mockito.verify(mockView).dismissProgressDialog();
}
So using Dagger I need to build Presenter correctly. For this purposes, I am trying to use Mockito.when. Firstly look like this line doesn't work
Mockito.when(mockModel.provideLoginModel(null)).thenReturn(mock);
The target purpose is to use my own Model realization which return Single.
don't really understand why my mock of ModelModule doesn't work?
What about creating a test module out of your production Module?
See how they suggest to do testing via Dagger in official site.
#Module
public class ModelModuleTest extends ModelModule {
#Override
public ILoginModel provideLoginModel(LoginModel p) {
...
}
}
You can pass mocked dependency to your Module.
UPDATED
The issue may be that you are mocking will null. In that case mockModel will only return mock when provideLoginModel is called with null
Mockito.when(mockModel.provideLoginModel(null)).thenReturn(mock);
mockModel.provideLoginModel(null) // returns mock
mockModel.provideLoginModel(new Foo()) // returns null
Instead you can use a matcher such as any():
Mockito.when(mockModel.provideLoginModel(any())).thenReturn(mock);
mockModel.provideLoginModel(null) // returns mock
mockModel.provideLoginModel(new Foo()) // also returns null
to return mock on any call.
BIG PICTURE
For unit testing I would suggest not using Dagger, instead use #Mock and #InjectMocks you only need the object you are testing to be real the rest can be mocks.
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml")
public class LoginPresenterTest {
public static final String SOME_OTHER_TOKEN = "someOtherToken";
#Mock
ILoginView mockView;
#Mock
SomePresenterDependency somePresenterDependency
#InjectMocks
ILoginPresenter mLoginPresenter;
#Before
public void setup() {
MockitoAnnotations.injectMocks(this);
mLoginPresenter.setView(mockView);
}
#Test
public void testLogin() {
mLoginPresenter.logUserIn("", "", "", "");
try {
java.lang.Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Mockito.verify(mockView).dismissProgressDialog();
}
}
If you are doing integration testing and need multiple real objects you could just create an inner/anonymous module for your component that returns the desired object. (instead of trying to mock the module interface).
I'm trying to create unitest case for retrofit 2 callbacks in android. I use for test mockito, MockWebServer and MockResponse.
public class LoginFragment extends Fragment {
/**
* Actualiza el numero telefonico para el usuario
*
* #param phoneNumber
*/
public void phoneNumber(String phoneNumber) {
HttpService service = Service.createService(HttpService.class, TOKEN);
Call<Void> call = service.phonumber(phoneNumber, new User("", ""));
call.enqueue(callback());
}
/**
* #return Callback<Void>
*/
public Callback<Void> callback() {
return new Callback<Void>() {
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
if (response.isSuccessful()) {
dummy();
} else {
Log.e(TAG, "problema");
}
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
Log.e(TAG, " " + t);
}
};
}
public void dummy(){
System.out.println(" called");
}
}
My unitest class:
#RunWith(MockitoJUnitRunner.class)
public class TestLoginFragment {
MockWebServer mockWebServer;
#Before
public void setup() throws Exception {
spyLoginFragment = mock(LoginFragment.class);
mockWebServer = new MockWebServer();
}
#Test
public void testDummyIsCalled() {
spyLoginFragment.phoneNumber("3333335");
mockWebServer.enqueue(new MockResponse().setResponseCode(201));
verify(spyLoginFragment, times(1)).dummy();
}
}
But when you run the test I get:
TestLoginFragment > testDummyIsCalled FAILED
Wanted but not invoked:
loginFragment.dummy();
I'm new making callback test, how can I verify that dummy() was called?
By definition, unit test only tests the functionality of the units themselves. Therefore, it may not catch integration errors.
You shouldn´t test the retrofit framework or its callbacks, you must be presumed that retrofit always works. Only Test your code, so create a test for phoneNumber(String phoneNumber) that checks if the service was configured correctly (not need to launch retrofit service), and create others test to check the posible responses from the server in OnSuccess or OnFailure cases.
PD: If you want to test the coupling between the Retrofit call and the callback's methods, then you're talking about "integration test".
I use MockRestAdapter to return mock data in my tests, but I'd also like to test errors (401, 503, UnknownHostException, etc)
For SocketTimeoutException, there's an API, but how about different response code?
I've tried MockWebServer but no matter what I enqueue, I always get a 200 with the mock data from the adapter.
update: I want to run my tests like this:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class LoginActivityTest {
#Test public void goodCredentials() {
activity.login("username", "password");
assert(...); // Got back 200 and user object (from mock)
}
#Test public void wrongCredentials() {
activity.login("username", "wrong_password");
something.setResponse(401, "{error: wrong password}");
assert(...);
}
#Test public void someError() {
activity.login("username", "password");
something.setResponse(503, "{error: server error}");
assert(...);
}
}
update 2:
Found something, rather ugly, but does what I need:
MockApi implements ServiceApi {
public static Throwable throwable;
#Override login(Callback<User> callback) {
if (throwable != null) {
sendError(callback)
} else {
callback.success(new User("{name:test}"));
}
}
private void sendError(Callback callback) {
callback.failure(RetrofitError.unexpectedError("", throwable));
}
}
public class LoginActivityTest {
#Test public void someError() {
MockApi.throwable = new InterruptedIOException()
activity.login("username", "password");
// Assert having a time out message
}
#Test public void someError() {
MockApi.throwable = new UnknownHostException()
activity.login("username", "password");
// Assert having a no internet message
}
}
Still working on it, so any feedback will help :)
It's fairly easy to do. You just need to implement Client and pass it when you build your mock RestAdapter.
Creating client with appropriate response:
Client client = new Client() {
#Override public Response execute(Request request) throws IOException {
final String reason = "Some reason.";
final List<Header> headers = new ArrayList<>();
final TypedString body = new TypedString("");//could be json or what ever you want
final int status = 401;
return new Response(request.getUrl(), status, reason, headers, body);
}
};
And passing it to your RestAdapter.Builder:
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.com")
.setClient(client)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
restAdapter.create(API.class);
In my Android application I have set up Volley.
Robolectric.application is initialized and all other tests runs smoothly.
I get this error when trying to get mocked HTTP response.
This is my test:
#RunWith(MyRobolectricTestRunner.class)
public class ApiTests {
#Inject
protected Api api;
#Before
public void setUp() {
ObjectGraph.create(new AndroidModule(Robolectric.application), new TestApplicationModule()).inject(this);
}
#Test
public void shouldGetErrorList() throws Exception {
Project project = new Project("test", "test", "test", DateTime.now());
addPendingProjectsErrorsResponse("response.json"); //adding response to FakeHttpLayer
api.getProjectErrors(project, new Listener<ProjectErrors>() {
#Override
public void onResponse(ProjectErrors response) {
assertNotNull(response);
}
}, new ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
throw new RuntimeException(error);
}
}
);
}
}
This is error I get:
Exception in thread "Thread-3" java.lang.NullPointerException
at org.robolectric.shadows.ShadowLooper.getMainLooper(ShadowLooper.java:59)
at android.os.Looper.getMainLooper(Looper.java)
at org.robolectric.Robolectric.getUiThreadScheduler(Robolectric.java:1301)
at org.robolectric.shadows.ShadowSystemClock.now(ShadowSystemClock.java:15)
at org.robolectric.shadows.ShadowSystemClock.uptimeMillis(ShadowSystemClock.java:25)
at org.robolectric.shadows.ShadowSystemClock.elapsedRealtime(ShadowSystemClock.java:30)
at android.os.SystemClock.elapsedRealtime(SystemClock.java)
at com.android.volley.VolleyLog$MarkerLog.add(VolleyLog.java:114)
at com.android.volley.Request.addMarker(Request.java:174)
at com.android.volley.CacheDispatcher.run(CacheDispatcher.java:92)
I had same error and avoid it by using my own (and ugly) SystemClock shadow.
shadow class:
#Implements(value = SystemClock.class, callThroughByDefault = true)
public static class MyShadowSystemClock {
public static long elapsedRealtime() {
return 0;
}
}
test code:
#Test
#Config(shadows = { MyShadowSystemClock.class, ... })
public void myTest() {
}
Another workaround would be to disable Volley logging by calling
VolleyLog.DEBUG = false;
in your setUp method.