-
Notifications
You must be signed in to change notification settings - Fork 8
Home
This is a demo project of a simple GitHub repository browser, it showcases a Dagger usage that help to write testable code.
The app contains just an Activity with a search field and a button, clicking on button a network call is executed to retrieve and show a list of github repository.
The first time in the morning the app is started a welcome message is displayed with a dialog.
A repository can be shared clicking on an item in the list.
https://github.com/fabioCollini/TestableAndroidApps/tree/00_start
The app is developed using standard Android components, network call is executed in an IntentService and the data are passed back to the Activity using a LocalBroadcastManager.
The activity is tested in a simple Espresso test:
public class MainActivityTest extends BaseActivityTest<MainActivity> {
public MainActivityTest() {
super(MainActivity.class);
}
public void testSearch() {
onView(withId(R.id.query))
.perform(ViewActions.typeText("abc"));
onView(withId(R.id.search))
.perform(click());
onData(is(instanceOf(Repo.class))).atPosition(3)
.perform(click());
}
}
This test works, but we can't trust it:
- the first time in the morning it doesn't work, there is the welcome dialog in front of the search field
- it requires an internet connection to work, you need to manually disable wifi to test reload button
- if github is down, the test fail (and we need to wait github is down to test error management)
- the share is not covered in this test, we don't know if it's invoked correctly
https://github.com/fabioCollini/TestableAndroidApps/tree/01_dagger
We introduced Dagger to test MainActivity ignoring the welcome dialog.
https://github.com/fabioCollini/TestableAndroidApps/tree/02_welcome_dialog_manager_tests
In 01_dagger we ignored welcome dialog, now it's time to test it using some test modules.
https://github.com/fabioCollini/TestableAndroidApps/tree/03_githubservice
We don't want to call remote server on each test, in this branch we created a stub class to load and parse a local file.
https://github.com/fabioCollini/TestableAndroidApps/tree/04_reload_button
We can test reload button using a stub that throws an exception the first time is used:
public class GitHubServiceErrorStub extends GitHubServiceStub {
private boolean firstTime = true;
public GitHubServiceErrorStub(Resources resources, int id) {
super(resources, id);
}
@Override public RepoResponse listRepos(String query) {
if (firstTime) {
firstTime = false;
throw new RuntimeException("Error");
}
return super.listRepos(query);
}
}
https://github.com/fabioCollini/TestableAndroidApps/tree/05_activity_module
Dagger scopes can be useful to avoid passing Context object everywhere. Here we create an ActivityModule to provide activity object:
@Module(injects = MainActivity.class, addsTo = AppModule.class)
public static class ActivityModule {
private FragmentActivity activity;
public ActivityModule(FragmentActivity activity) {
this.activity = activity;
}
@Provides @Singleton public FragmentActivity provideActivity() {
return activity;
}
}
We inject activity directly in other objects:
@Singleton
public class WelcomeDialogManager {
@Inject FragmentActivity activity;
@Inject Clock clock;
@Inject DatePrefsSaver datePrefsSaver;
public void showDialogIfNeeded() {
if (isMorning() && !datePrefsSaver.isTodaySaved()) {
new WelcomeDialog().show(activity.getSupportFragmentManager(), "welcome");
datePrefsSaver.saveNow();
}
}
public boolean isMorning() {
int hour = clock.now().get(GregorianCalendar.HOUR_OF_DAY);
return hour > 6 && hour < 12;
}
}
Finally we replaced IntentService and LocalBroadcastManager with otto.