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).

Update Your Fake AndroidInjector

Using Dagger 2 for dependency injection means that you can inject fake / mock objects for testing. I had been using a fake AndroidInjector to do this in my Espresso tests for an old Android app I was updating, but found that on updating Dagger to a more recent version (from 2.14 to 2.21) the custom AndroidInjector no longer compiled.

The fake AndroidInjector I was using was based on these blogs that were written a couple of years ago:

Another version of that idea can be found here:

This is a quick post on how this can be fixed for anyone who is still using the code from these articles (also a  belated thank- you to these authors for coming up with these ideas).

It Does Not Compile …

The problem came with these internal changes to the Dagger code in version 2.19.

https://github.com/google/dagger/releases/tag/dagger-2.19

Simple Fixes

Based on the code from the articles mentioned above, just need some simple changes (that unfortunately took me a while to work out!).
1. Replace the deprecated annotation

@ActivityKey

with

@ClassKey

2. The generic typing for AndroidInjector.Factory and other classes used in the AndroidInjector code have changed from

<? extends Activity> // java
<out Activity> // kotlin

to

<?> // java
<*> // kotlin

3. The DispatchingAndroidInjector  has changed it’s  constructor signature. Instead of having a single parameter of a Map of the Provider  of the AndroidInjector.Factory keyed by class, there is now an additional parameter of a Map using a String (class name) as the key

So in the Dagger generated code (and in the fake AndroidInjector), the method DispatchingAndroidInjector_Factory.newDispatchingAndroidInjector() also needs this extra parameter (even if just an empty Map).

// java (pseudo-code)

Map<Class<?>, Provider<AndroidInjector.Factory<?>>> classMap = new HashMap<>(1);
// create a custom AndroidInjector.Factory and add it to the provider
Provider<AndroidInjector.Factory<?>> provider = ...
map.put(MyActivity.class, provider);

// empty map to satisfy method signature for newDispatchingAndroidInjector()
Map<String, Provider<AndroidInjector.Factory<?>>> stringMap = new HashMap<>();

return DispatchingAndroidInjector_Factory.newDispatchingAndroidInjector(classMap, stringMap);
// kotlin

val classMap : Map<Class<*>, Provider<AndroidInjector.Factory<*>>> = mapOf(
Pair<Class<*>, Provider<AndroidInjector.Factory<*>>>(T::class.java, Provider { factory }))

// empty map to satisfy method signature for newDispatchingAndroidInjector()
val stringMap : Map<String, Provider<AndroidInjector.Factory<*>>> = emptyMap<String, Provider<AndroidInjector.Factory<*>>>()

return DispatchingAndroidInjector_Factory.newDispatchingAndroidInjector<Activity>(classMap, stringMap)

Why use a fake AndroidInjector?

The more common way to have Dagger inject test dependencies is to maintain a parallel universe of test components and test modules to provide the fake dependencies. This works fine and has the advantages that it shouldn’t break due to internal changes in the Dagger code. However it also means more boilerplate code to maintain.

 

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&amp;lt;Activity&amp;gt; dispatchingAndroidInjector;
  .
  .
  .
  @Override
  public DispatchingAndroidInjector&amp;lt;Activity&amp;gt; activityInjector() {
    return dispatchingAndroidInjector;
  }
}

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

  @Override
  public AndroidInjector&amp;lt;Activity&amp;gt; activityInjector() {
    return new AndroidInjector&amp;lt;Activity&amp;gt;() {
      @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.