Espresso Idling Resource for RecyclerView Data Changes

I was having a problem with using Android Espresso to test a RecyclerView when it’s data was updated.

This is for an Android app where a list of contacts is displayed by a RecyclerView. There is a SearchView in the action bar that can filter the contacts list to display matching contact names.

The Espresso test ran like this:

  • Start the activity.
  • Espresso verifies that the full list of contacts is displayed in the RecyclerView. This works fine.
  • A query string is entered in a SearchView, and the filtering of the data in the RecyclerView is initiated (I’m using a SearchView to get the query string, but other controls such as EditText, etc, could be used instead).
  • Espresso verifies that the list of contacts has changed to only display matching items. Fails.
@RunWith(AndroidJUnit4.class)
public class RecyclerViewIdlingResourceTest {

@Rule
public ActivityRule<MainActivity> activityRule = new ActivityRule<MainActivity>(MainActivity.class);

// number of items in the original list
int allItemsCount = ...;

// number of items after the list has been filtered
int filteredItemsCount = ...;

@Test
public void testRecyclerviewFilter()
{
  // verify all test items loaded
  // SUCCESS
  onView(withId(R.id.recyclerview)).check(withItemCount(allItemsCount));

  // since the search view is initially collapsed, open it first before     tests are run
  onView(withId(R.id.action_search)).perform(click());

  // enter some text into the search view, and then press the action button.
  String searchText = "test"
  onView(withId(android.support.design.R.id.search_src_text)).perform(typeText(searchText), pressImeActionButton());

  // verify the number of items in the recyclerview list has been altered
  // FAIL!
  onView(withId(R.id.recyclerview)).check(withItemCount(filteredItemsCount));
}
}

Unfortunately it seems like that the Espresso assert to verify that the list of items has changed happens before the RecyclerView has finished reloading the updated data and redrawing itself. So the test fails when it finds that the RecyclerView still has the original number of items because it has not yet redrawn itself with the new list of data.

The code for this post is in this gist. It is in the form of incomplete code that only includes stuff relevant to the post. Also there are various ways of implementing and filtering a RecyclerView, so I will leave that part to the reader.

So, What’s the Problem?

After I have changed the data for the RecyclerView, I am calling notifyDataSetChanged() on the adapter.

Based on this StackOverflow question, the issue seems to be that when notifyDataSetChanged() is called, it only invalidates the data in the RecyclerView, but doesn’t update the widget immediately. Hence I suspected that the Espresso assertion was happening before the RecyclerView had updated.

To test this, I introduced a pause before the Espresso assertion to allow time for the RecyclerView to update, and the test passed.

@Test
public void testRecyclerviewFilterWithPause()
{
  // verify all test items loaded
  // SUCCESS
  onView(withId(R.id.recyclerview)).check(withItemCount(allItemsCount));

  // since the search view is initially collapsed, open it first before tests are run
  onView(withId(R.id.action_search)).perform(click());

  // enter some text into the search view, and then press the action button.
  String searchText = "test"
  onView(withId(android.support.design.R.id.search_src_text)).perform(typeText(searchText), pressImeActionButton());

  // pause for arbitrary period of time, InterruptedException handling left out to simplify example
  Thread.sleep(1000);

  // verify the number of items in the recyclerview list has been altered
  // SUCCESS - assuming the pause time was long enough
  onView(withId(R.id.recyclerview)).check(withItemCount(filteredItemsCount));
}

Here I was using Thread.sleep() but any Android equivalent with Handlers, etc, would have done too. Of course this is all a bit of a hack. The recommended way to introduce a wait for some process to complete before Espresso continues to test is to use Idling Resources.

Using a pause for some arbitrary time period is less than ideal, as it often leads to either flaky tests or making the tests run longer than necessary.

The RecyclerView Callback

For an Idling Resource to work, it needs to know when the RecyclerView RecyclerView is redrawing with the new list data.

There are various callbacks that the RecyclerView (and its support classes) have that can signal that the RecyclerView is in the process of redrawing. After searching on StackOverflow, I found these possibilities:

I decided to use onGlobalLayoutListener, but there seem to be multiple ways for the RecyclerView to signal that it being redrawn.

The Idling Resource is listening in …

Firstly we need some interfaces to use as callbacks to communicate between the RecyclerView, the activity/fragment containing the RecyclerView and Idling Resource.

Firstly here is an interface for the RecyclerView to notify the activity when the redrawing process with new data has occurred.


public interface RecyclerViewIdlingCallback {

public void setRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener);

public void removeRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener);

// Callback for the idling resource to check if the resource (in this example the activity containing the recyclerview)
// is idle
public boolean isRecyclerViewLayoutCompleted();
}

Then another interface to use as a callback for the activity to notify the Idling Resource to .. idle.


public interface RecyclerViewLayoutCompleteListener {

// Callback to notify the idling resource that it can transition to the idle state
public void onLayoutCompleted();
}

Here is an example activity, showing just the relevant code to work with the idling resource.

public class RecyclerViewCallbackContactsActivity extends AppCompatActivity implements
  SearchView.OnQueryTextListener,
  ViewTreeObserver.OnGlobalLayoutListener,
  RecyclerViewIdlingCallback {

  /**
   * Flag to indicate if the layout for the recyclerview has complete. This should only be used
   * when the data in the recyclerview has been changed after the initial loading.
   */
  private boolean recyclerViewLayoutCompleted;

  /**
   * Listener to be set by the idling resource, so that it can be notified when recyclerview
   * layout has been done.
   */
  private RecyclerViewLayoutCompleteListener listener;

  @Override
  public void onCreate (Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // CODE HERE to initialize the recyclerview

    recyclerViewLayoutCompleted = true;
    recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(this);
  }

  @Override
  public boolean onQueryTextSubmit(String query) {

    // CODE HERE to filter the recyclerview using the query string,
    // - this should eventually result in notifyDataSetChanged() being called on the adapter
        
    // flag that a new layout will be required with the filtered data
    recyclerViewLayoutCompleted = false;
  }

  @Override
  public void onGlobalLayout() {
    if (listener != null)
    {
      // set flag to let the idling resource know that processing has completed and is now idle
      recyclerViewLayoutCompleted = true;

      // notify the listener (should be in the idling resource)
      listener.onLayoutCompleted();
    }
  }

  @Override
  public boolean isRecyclerViewLayoutCompleted() {
    return recyclerViewLayoutCompleted;
  }

  @Override
  public void setRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener) {
    this.listener = listener;
  }

  @Override
  public void removeRecyclerViewLayoutCompleteListener(RecyclerViewLayoutCompleteListener listener) {
    if (this.listener != null && this.listener == listener)
    {
      this.listener = null;
    }
  }
}

The important parts in the activity are:

  • the listener method, onGlobalLayout(), which signals the recyclerview has inflated it’s layout for the redraw
  • the Boolean flag, recyclerViewLayoutCompleted, which is used by the idling resource to check if the Espresso test can continue to run after the recyclerview redraw.

This is the idling resource to be used to test the recyclerview in the activity.

public class RecyclerViewLayoutCompleteIdlingResource implements IdlingResource {

  private ResourceCallback resourceCallback;
  private RecyclerViewIdlingCallback recyclerViewIdlingCallback;
  private RecyclerViewLayoutCompleteListener listener;

  public RecyclerViewLayoutCompleteIdlingResource(RecyclerViewIdlingCallback recyclerViewIdlingCallback){
    this.recyclerViewIdlingCallback = recyclerViewIdlingCallback;

    listener = new RecyclerViewLayoutCompleteListener() {

      @Override
      public void onLayoutCompleted() {
        if (resourceCallback == null){
          return ;
        }
        if (listener != null) {
          recyclerViewIdlingCallback.removeRecyclerViewLayoutCompleteListener(listener);
        }
        //Called when the resource goes from busy to idle.
        resourceCallback.onTransitionToIdle();
      }
    };

    // add the listener to the view containing the recyclerview
    recyclerViewIdlingCallback.setRecyclerViewLayoutCompleteListener (listener);
  }
  @Override
  public String getName() {
    return "RecyclerViewLayoutCompleteIdlingResource";
  }

  @Override
  public boolean isIdleNow() {
    return recyclerViewIdlingCallback.isRecyclerViewLayoutCompleted();
  }

  @Override
  public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
    this.resourceCallback = resourceCallback;
  }
}

The activity is passed to the idling resource in it’s constructor as an RecyclerViewIdlingCallback implementation. Then when the recyclerview in the activity is ready, the activity will invoke the callback in the idling resource to indicate that it is ‘idle’.

Finally we can put this together in an Espresso test.

@Test
public void testFilterRecyclerViewUsingSearchView()
{
  // CODE HERE use espresso to use the SearchView to filter the recyclerview

  RecyclerViewLayoutCompleteIdlingResource idlingResource = new RecyclerViewLayoutCompleteIdlingResource((RecyclerViewCallbackContactsActivity) activityTestRule.getActivity());
  IdlingRegistry.getInstance().register(idlingResource);

  // CODE HERE to verify the recyclerview with the updated data

  IdlingRegistry.getInstance().unregister(idlingResource);
}

Caveat
I actually started writing this post a while ago, so the code was for the Recyclerview from the Android support library rather than from the Androidx library.

Restart Android Activity with ActivityScenario

I was writing an instrumentation test which required restarting the activity during a test. As I was trying out the ActivityScenarioRule to replace an ActivityTestRule, the documentation says I can use this method on the ActivityScenario to restart the activity after it has been launched:

scenario.recreate()

So I wrote this function to for restarting the activity:

    @Rule
    @JvmField var rule = ActivityScenarioRule(MyActivity::class.java)
	
    fun restartActivity() {
        var scenario = rule.getScenario()
        scenario.recreate()
    }

However I was getting an error message for androidx.test.core.app.InstrumentationActivityInvoker.

In source code for InstrumentationActivityInvoker, looking at the method recreateActivity(), the comments section explains why there may be some indeterminate behaviour depending  on the state of the activity and the version of Android being run on.

Recreates the Activity by {@link Activity#recreate}.

Note that {@link Activity#recreate}’s behavior differs by Android framework version. For example, the version P brings Activity’s lifecycle state to the original state after the re-creation. A stopped Activity goes to stopped state after the re-creation in concrete.
Whereas the version O ignores {@link Activity#recreate} method call when the activity is in stopped state. The version N re-creates stopped Activity but brings back to paused state instead of stopped.

In short, make sure to set Activity’s state to resumed before calling this method otherwise the behavior is the framework version dependent.

So for my particular test, the recreate() method wasn’t working (although it might have worked for someone else for a different test).

A simple change to my function fixed this problem:

    fun restartActivity() {
        var scenario = rule.getScenario()
        scenario.moveToState(Lifecycle.State.RESUMED)
        scenario.recreate()
    }

Of course it seems that ActivityScenario and ActivityScenarioRule are still a bit of a work in progress for now, so hopefully the documentation will catch up some time (or they may change the code again in future versions).

Mocking Static Methods in Android: Let’s Wrap it up

When writing local unit tests in Android, one of the limitiations that you face is that the tests are run against a version of android.jar that does not have any code. As the documentation explains, any dependency on Android code must be mocked.

A quick example of a simple unit test:

public class ClassUnderTest {

public String methodUnderTest(String str)
{
    if (PhoneNumberUtils.isGlobalPhoneNumber(str))
    {
      return "yes";
    }
    else
    {
      return "no";
    }
  }
}

@RunWith(JUnit4.class)
public class TestThatFails {

private ClassUnderTest classUnderTest;

  @Before
  public void setup() {
    classUnderTest = new ClassUnderTest();
  }

  @Test
  public void testTheClass() {
    String result = classUnderTest.methodUnderTest("1234");
    assertEquals("yes", result);
  }
}

When this test is run, it will fail with the following error:

java.lang.RuntimeException: Method isGlobalPhoneNumber in android.telephony.PhoneNumberUtils not mocked. See http://g.co/androidstudio/not-mocked for details

The class we are testing has a dependency on the Android utility library PhoneNumberUtils. In order for the test to run successfully, this Android dependency must be mocked.

All the example code for this post is available at this gist.

Mockito: No to Static Methods

Google’s suggested way to mock Android dependencies is to use Mockito. This would generally be fine, however in our example this will not work because Mockito does not support mocking static methods.

This discussion shows that the Mockito contributors consider static methods to be an anti-pattern for various reasons, e.g.

  • The dependency on the static method becomes hard wired in the code.
  • This makes mocking and testing difficult.

Hence they do not support it as they don’t want to encourage poor design.

So what are some other way to make our test work?

  • If this was plain old Java instead of Android, I could have used PowerMockito to mock the static methods. However I have found using PowerMock to be problematic in Android.
  • If you only use a few static methods, you could just copy the code into your app assuming the the source was available. Of course this means more code to maintain, and is not sustainable if you use a lot of static methods.
  • You could wrap the static method call and internally delegate to the static method. The wrapper could then be mocked. This is the option we will be discussing.

Wrapper Classes

One solution is to create a wrapper class for the Android classes that have the static method, and add that wrapper as a dependency.

public class PhoneNumberUtilsWrapper {

  public boolean isGlobalPhoneNumber(String phoneNumber)
  {
    return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber);
  }
}

public class ClassUnderTestWithWrapper {

  private PhoneNumberUtilsWrapper wrapper;

  public ClassUnderTestWithWrapper(PhoneNumberUtilsWrapper wrapper) {
    this.wrapper = wrapper;
  }

  public String methodUnderTest(String str)
  {
    if (wrapper.isGlobalPhoneNumber(str))
    {
      return "yes";
    }
    else
    {
      return "no";
    }
  }
}

Here I have created a wrapper class for PhoneNumberUtils which is now a dependency of the class under test.

@RunWith(JUnit4.class)
public class TestWithWrapper {

  @Mock
  PhoneNumberUtilsWrapper wrapper;

  private ClassUnderTestWithWrapper classUnderTest;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);

    classUnderTest = new ClassUnderTestWithWrapper(wrapper);
  }

  @Test
  public void testTheClass() {
    when(wrapper.isGlobalPhoneNumber(anyString())).thenReturn(true);

    String result = classUnderTest.methodUnderTest("1234");
    assertEquals("yes", result);
  }
}

Because the wrapper class can be mocked, and the method call in the class under test is not static, the test can now pass.

One problem with this solution is when the class under test depends on static methods from many Android libraries. For instance, what happens if the class under test also needs to use TextUtils, DateUtils, etc. Suddenly you end up with lots more boilerplate code, more constructor parameters, etc.

Wrapper Methods

Another way is to wrap the static method call in a non-static method in the class under test.

public class ClassUnderTestWithWrappedMethod {

  public String methodUnderTest(String str)
  {
    if (isGlobalPhoneNumber(str))
    {
      return "yes";
    }
    else
    {
      return "no";
    }
  }

  // can't be private access
  boolean isGlobalPhoneNumber(String phoneNumber)
  {
    return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber);
  }
}

In order for this to work, in the test we have to use Mockito spy. Also note that the wrapped methods have to be accessible in the test, and therefore can’t be private.

@RunWith(JUnit4.class)
public class TestWithWrappedMethod {

  private ClassUnderTestWithWrappedMethod classUnderTest;

  private ClassUnderTestWithWrappedMethod classUnderTestSpy;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);

    classUnderTest = new ClassUnderTestWithWrappedMethod();
    classUnderTestSpy = Mockito.spy(classUnderTest);
  }

  @Test
  public void testTheClass() {
    doReturn(true).when(classUnderTestSpy)
    .isGlobalPhoneNumber(anyString());

    String result = classUnderTestSpy.methodUnderTest("1234");
    assertEquals("yes", result);
  }
}

Here we are running the test on the spy class, which will delegate method calls to the real class under test. However we can create stubs for the methods that wrap the static method calls to Android libraries.

As I have mentioned, one disadvantage is that the wrapped methods cannot be private, which is not ideal from an OO design point of view. But then you have to make similar compromises if you are using libraries such as Dagger or Butterknife.

Conclusion

Both of these wrapping solutions can work, but it’s probably a good idea to be consistent and stick with doing it one way if you can. Which method works better may depend on your app architecture, e.g. are you using dependency injection.

Static Methods: Good or Bad? Does it matter?

In this post I am not getting into the argument about whether static methods are good or bad design (although my personal opinion is that their use should be limited). There are plenty of arguments about this issue on the internet already.

However, in the Java world they are a fact of life.

Many utility classes in Java, Android and many popular libraries are not real OO classes, but collections of procedural functions. The functions are often written as static methods and grouped by functionality.

Whether you like static methods or not, we all have to learn to deal with them in a pragmatic way.

Android Studio 3.0 – Initial Impressions of Tool Support

I have been using Android Studio 3.0 since since the alpha versions, and it is good to see it finally released.

This is just some initial comments on using some common and new Android tools and libraries with the 3.0 version of Android Studio and the associated Android Gradle plugin.

Hopefully this will be useful for anyone thinking of upgrading from Android Studio 2.x.

Java 8 Finally, Maybe

Java 8 was released back in 2014, and with Android Studio 3.0 it is finally supported in Android. Finally I can say goodbye to RetroLambda (although many thanks to the authors for this very useful library).

Just be aware that many of the Java 8 language features will only be available if the minSkdVersion is 24.

Since devices running Nougat or Oreo are still in the minority, supporting older versions will continue to be an issue for a while if you want to use Java 8. Some ideas floated around to support older API version, are to have min API version flavors or  do Build.VERSION checks for the API version in the code to provide alternative code paths.

Desugar

The Jack tool chain has been replaced with the desugar tranformation process to support Java 8. Some older libraries and code may have some problems with this build stage.

JUnit 5

JUnit 5 has also been released recently, and with the help of this plugin you can use it for unit testing in Android Studio.

However if you use JUnit 4 Rules in your tests, there is limited support in JUnit 5 as they are meant to be replaced with extensions. Some Rules may be supported with the JUnit 5 migration support, but if not you will have to wait until the Rules are rewritten as extensions or not use the Rules at all.

Of course this is only for unit testing. Instrumentation tests still depend on JUnit 4 and the AndroidJUnitRunner.

Update (7 Dec 2017)

The authors of the JUnit5 plugin have stated that they now have support for JUnit 5 in instrumentation tests. I won’t be using it for a while since it requires the minSkdVersion to be 26. If anyone has tried this, please let me know how well it works.

Architecture Components, RxJava

I have also been using some of Google’s Architecture Components, but only the ViewModel and LiveData. This is another alternative to design patterns such as MVP or MVVM with Databinding.

If you use RxJava, then this will of course continue to work as it is compatible with Java 6. You may consider replacing RxJava with the more light weight LiveData for activities and fragments, and they can be adapted to each other.

Dependency Injection with Dagger

I have had no problems with Google’s Dagger, but be aware of the API changes for the Android Gradle plugin 3.0 in the gradle build file (e.g. implemention instead of compile, annotationProcessor instead of apt, etc).

See the Android Gradle plugin 3.0 migration guide.

Butterknife, Timber

Some common utilities like Butterknife and Timber for logging are working fine. I’m also using the slf4j binding for Timber without any trouble.

JRebel for Android

During the Android Studio alpha and beta process, JRebel for Android was always playing catch up with the latest version. I still have issues with the current version (2.4.14) of their free edition, so I am using instant run instead at the moment.

I’m a big fan of JRebel so I’m hoping they will have a stable version of their android plugin for Android Studio 3.0 soon.

Update (31 Jan 2018)

JRebel for Android is no longer being actively developed.

AOP with AspectJ

There are various AspectJ plugins for Android,  and the one I have been using in Android Studio 2.x was this one. Unfortunately it doesn’t support the Android plugin for Gradle 3.0 yet, but the author seem to be working on it so hopefully the support will be there soon.

Update (15 Nov 2017)

Version 3.2.0 of the GradleAspectJ-Android plugin now supports the Android plugin for Gradle 3.0. I like this plugin because it supports both aj files and annotation style aspects.

Things will get better

Of course since Android Studio 3.0 has only recently got general release, we should expect tool and library support to improve going forward.

I have only scratched the surface with some common Android tools and libraries, and there are many more around. Please let me know of your experiences with others.

Android UI Test: Mocking the ViewModel with or without Dagger? Part 2

In the first part of this post, I explored the approach of setting up a UI test with a mock ViewModel without using Dagger 2 for dependency injection. I used the GithubBrowserSample app from the Architecture Components sample code to demonstrate disabling Dagger for UI testing, even though the app itself uses Dagger.

Now, using Dagger

There are various ways of using Dagger to provide fake or mock version of dependencies for testing. Generally they involve writing test versions of the component and module classes.

Then when the Espresso test is run, somehow the Dagger object graph that is built (incorporating the mock dependencies provided by the test module) is used instead of the one used just for the app. Some of the ways to do this includes:

  • Include a hook into the application class to replace the Dagger components with the test versions.

http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html

  • Once again create a test version of the application class in the androidTest directory. Here the test application would be a subclass of the app application class where the code to build the Dagger graph is overriden with the test version.

http://blog.sqisland.com/2015/12/mock-application-in-espresso.html

Of course, this would mean writing a custom test runner to use instead of AndroidJUnitRunner in the gradle build file.

public class YourApp extends Application implements hasActivityInjector {

  @Inject
  DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
  .
  .
  .
  @Override
  public DispatchingAndroidInjector<Activity> activityInjector() {
    return dispatchingAndroidInjector;
  }
}

// The test app in the androidTest directory
public class TestApp extends YourApp {

  @Override
  public AndroidInjector<Activity> activityInjector() {
    return new AndroidInjector<Activity>() {
      @Override
      public void inject(Activity instance) {
        // inject the fake / mock dependencies into the activity
        // e.g.
        .
        .
        .
        ((YourActivity)instance).viewModelFactory = ...
      }
    };
  }
}

Other options I came across in my research include:

  • There is the DaggerMock library that uses a JUnit Rule to override Dagger objects. This is a nice idea, but currently DaggerMock only has limited support for Dagger Android. In particular it does not handle abstract modules and methods which some of the Dagger Android annotations depend on.
  • Include a hook in the activity to set dependencies.

https://blog.stylingandroid.com/architecture-components-summary/

Disadvantages, again

As I mentioned in the previous post, any of these approaches that uses a custom test application class for instrumentation testing would apply the same Dagger object graph to all the tests. This is not suitable for my situation where I only want to mock the ViewModel for the UI tests, but use the real one for other tests.

Other ways require making code changes to the app just to accommodate testing. This is a bit of a hack and not good design.

One Possible Solution

The solution I decided to use was based on this gist. The test module was used to create a AndroidInjector that would inject a custom ViewModel factory into the test activity. In turn the custom ViewModel factory would provide the mock ViewModel.

.
.
return new AndroidInjector<MyActivity>() {
  @Override
  public void inject(MyActivity instance) {
    // create the viewmodel mocks
    MyViewModel viewModel = Mockito.mock(MyViewModel.class);

    // create the livedata used to return results
    MutableLiveData<Data> returnedData = new MutableLiveData<>();
    when(viewModel.getData()).thenReturn(returnedData);

    // set test data
    Data expectedData = ...
    returnedData.setValue(expectedData);

    // set the custom viewmodel factory that just returns the mocks
    instance.viewModelFactory = ViewModelUtil.createFor(viewModel);
  }
};

This approach did have the downside of requiring a lot of boilerplate code. But it also allowed me to only mock the ViewModel for the UI tests, without affecting the other tests.

Disclaimer

Please note that the opinions expressed in this post are not meant to apply to all instrumentation testing with Dagger.

They are only for the specific use case of trying to mock the ViewModel for my UI tests, while not affecting other instrumentation tests.

 

Android UI Test: Mocking the ViewModel with or without Dagger?

An Android app I’m currently working on has the following architecture:

When it came to testing the views , I wanted to write some UI tests using Espresso. These would just test just the activities and fragments with a mock backend.

With the Architecture Components I thought this would be fairly simple since all the interactions between the view and the backend services should be done through the ViewModel. Hence all I would need to do is to provide a mock ViewModel (in my case using Mockito).

Additionally I wanted to find a solution that:

  • was simple and straightforward, without the need for workarounds or hacks if possible
  • did not require any changes or additional code in the app just to accommodate testing

Looking at the Google sample code – no Dagger for UI Tests!

Since Google provides sample code for the Architecture Components, that’s the first place I looked. In particular the GithubBrowserSample seemed to be what I was after, this is from the README:

UI Tests

The projects uses Espresso for UI testing. Since each fragment is limited to a ViewModel, each test mocks related ViewModel to run the tests.

However, when I looked at the sample code for their UI tests, I was in for a surprise. Although the sample app itself uses Dagger DI, the UI tests do not.

This differs from most examples of testing Dagger applications, where it is advocated to write additional Components and Modules to inject fake dependencies for testing.

How it is done? The Setup.

Since I couldn’t find any documentation for these UI tests , here is a quick summary of how the GithubBrowserSample apps mocks the ViewModel without using Dagger. This basically involves using test version of the application and activity classes which do not invoke the Dagger injection code.

ViewModel Factory

Although it is possible to inject a ViewModel into an activity or fragment with Dagger, I will be using a custom ViewModel factory and injecting that instead. This is done for the following reasons:

  • Injecting a ViewModel class is only possible for ViewModels that have a default (empty) constructor. If the ViewModel constructor has parameters, then you need to inject a factory class that implements ViewModelProvider.Factory.
  • It seems to be a common pattern when using Dagger with the ViewModel to create a Module to encapsulate the ViewModel injection code. This Module would bind the ViewModel classes used in the app into a map. It would also provide the ViewModel factory class, which in turn uses the map to create the ViewModel classes. This is the pattern used in the GithubBrowserSample.

Here is a brief description of the UI tests for the fragments in the sample code:

  • Create a test application class.

This is just a dummy application class that does not invoke the Dagger code that builds the object graph.

https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/androidTest/java/com/android/example/github/TestApp.java

  • Create a test activity class.

This is just a dummy activity class to contain the fragment to be tested. It allows the fragment to be inserted into it.

https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/debug/java/com/android/example/github/testing/SingleFragmentActivity.java

This test runner will used the test application class instead of the application class for the app.

https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/androidTest/java/com/android/example/github/util/GithubTestRunner.java

@Override
 public Application newApplication(ClassLoader cl, String className, Context context)
 throws InstantiationException, IllegalAccessException, ClassNotFoundException {
   return super.newApplication(cl, TestApp.class.getName(), context);
}
android {
  .
  .
  .
  testInstrumentationRunner "com.android.example.github.util.GithubTestRunner"
}

How it is done? The UI Test.

The GithubBrowserSample has several UI tests for different fragments, but they follow the same basic pattern. Let’s use this one as an example:

https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/androidTest/java/com/android/example/github/ui/user/UserFragmentTest.java

Remember that when the UI tests are run, there is no Dagger dependency injection.

  • Use the test activity class to hold the fragment to be tested, instead of the activity used in the app. This is done in the ActivityTestRule used in setting up Espresso tests.
@Rule
public ActivityTestRule&lt;SingleFragmentActivity&gt; activityRule =
new ActivityTestRule&lt;&gt;(SingleFragmentActivity.class, true, true);
  • Before the test is run, setup the mock ViewModel.
@Before
public void init() {
.
.
  viewModel = mock(UserViewModel.class);
  when(viewModel.getUser()).thenReturn(userData);
  when(viewModel.getRepositories()).thenReturn(repoListData);
.
.
}
  • Set the ViewModel factory field in the fragment (which would have been injected in the app) to a fake factory class that just passes the mocked ViewModel. Of course the field needs to be accessible from the test class for this to happen (i.e. public or default package access).
@Before
 public void init() {
.
.
  fragment.viewModelFactory = ViewModelUtil.createFor(viewModel);
.
.
 }
  • Put the fragment into the test activity.
@Before
 public void init() {
.
.
.
  activityRule.getActivity().setFragment(fragment);
 }
  • Because this fragment uses LiveData to get data from the ViewModel, the test data is inserted into the LiveData returned from the mock ViewModel.
@Test
public void loadingWithUser() {
  User user = TestUtil.createUser("foo");
  userData.postValue(Resource.loading(user));
.
.
}

A simple approach

That’s it.

With this approach there is no need to worry about the Dagger configuration. Just mock the ViewModel to return the data you want for the Espresso test.

Disadvantages

  1. Using a custom test runner and test application means that all of instrumentation tests in the androidTest directory will have Dagger disabled.

But what if you have other instrumentation tests where you do want to use the Dagger injected classes, in additition to the UI tests. You don’t have the flexibility of deciding whether to enable / disable the Dagger DI on a test by test basis.

2. If you are using the Dagger Android library, then this approach will work with fragments, but not with activities. This is because an AndroidInjector is used in the onCreate() method of the activity to inject the dependencies.

@Override
public void onCreate (Bundle savedInstanceState) {
  AndroidInjection.inject(this);

  super.onCreate(savedInstanceState);
.
.
}

In the next post, I will explore some of the options if we want to mock the ViewModel in the UI tests with Dagger.