Back

Android Unit Testing Libraries

In the past few development posts, Ron has given us insight into several aspects of Android unit testing. He began with an overview of unit testing concepts and then discussed JUnit, the framework that Android testing is built upon. Today, he rounds out the series with an article on some specific Android libraries that can make unit testing a whole lot easier.

Roboguice

Roboguice is a Dependency Injection framework for Android built on top of Google Guice. It is by no means the only DI framework; Square produces a DI library called Dagger that is also popular, and in fact the majority of our Android projects have migrated from Roboguice to Dagger. Regardless, the core concepts of a DI framework are transferable, and selection of a DI framework and mastering its idiosyncrasies are beyond the scope of this article.

Roboguice can be added to a Gradle build via

compile 'org.roboguice:roboguice:2.0.+'

 

Injection

For objects instantiated by or controlled by Roboguice, injection is as simple as using the @Inject annotation:

public class ContactsFragment extends RoboFragment {    @Inject    private NetworkUtil networkUtil;    @Inject    private Preferences preferences;    public void onCreate() {      super.onCreate(); //injection occurs!      //I can now reference my @Injected fields!    }}

 

Singletons

/*  annotated with @Singleton means Roboguice will create a single RG-managed  instance for me that I can inject elsewhere */@Singletonpublic class Preferences {  @Inject  private SharedPreferences sharedPreferences;  //...}

 

Other Configuration

Roboguice is configured via modules. For your vanilla Android application scenario, the framework will read the file res/values/roboguice.xml to determine what configuration modules exist:

In test scenarios (more on that shortly), some of this can be specified/overridden programmatically.

Configuration in a module is useful/necessary at times when one can’t annotate the source classes (e.g., you’d like to make a Singleton out of a class provided by a library) or when additional or more complex initialization is required:

<?xml version="1.0" encoding="utf-8"?>  <resources>    <string-array name="roboguice_modules">    <item>com.mypackage.MyModule</item>  </string-array></resources>

 

RoboConfigModule.java

public class RoboConfigModule extends AbstractModule {  private FooUtil fooUtil;  private BarFactory barFactory;  @Override  protected void configure() {      if (Log.isLoggable("RoboConfigModule", Log.VERBOSE)) {        Log.v("RoboConfigModule",            "RoboConfigModule#configure() for instance " + hashCode());      }  }  //FooUtil needs a call to initialize() after its dependencies have been injected  @Provides  FooUtil provideFooUtil(Context context) {      if (fooUtil == null) {          fooUtil = new FooUtil();          final Injector injector = RoboGuice.getInjector(context);          injector.injectMembers(fooUtil);          fooUtil.initialize();      }      return fooUtil;  }   /*     Third-party class that we can't annotate with @Singleton, and we need to     initialize it with static call anyway   */  @Provides  BarFactory provideBarFactory(Application application) {    if (barFactory == null) {        String appId = Constants.APPLICATION_CODE;        String appDescription = application.getPackageName();        BarConfiguration barConfiguration = new BarConfiguration(appId,            appDescription);        barFactory = BarFactory.initialize(application.getApplicationContext(),            barConfiguration);    }    return barFactory;  }}

 

Roboguice With Tests

A particular challenge with the Android test framework is that calling getActivity() in your test both instantiates your Activity and calls onCreate(). This can give you fits if you were intending to inject mock objects for use in onCreate() because you’ve got no window to use setters before onCreate() is called.

Our solution to this is to create our mock objects in a TestRoboConfigModule, and then we use the normal DI framework to inject the mock objects into the unit under test. When this is an Activity, this means our mock objects are used in onCreate() (or anywhere else in the Activity) as desired.

For classes not associated with an Android lifecycle, we may choose whether to have DI framework inject the mock objects (which causes tests to have a bit more consistent look) or not (which is usually simpler to implement).

 

TestRoboConfigModule

public class TestRoboConfigModule extends AbstractModule {  private ChatClient client = mock(ChatClient.class);  private Preferences preferences = mock(Preferences.class);  private NetworkUtil networkUtil = mock(NetworkUtil.class);  private GroupUtil groupUtil = mock(GroupUtil.class);  @Override  protected void configure() {      bind(ChatClient.class).toInstance(client);      bind(Preferences.class).toInstance(preferences);      bind(NetworkUtil.class).toInstance(networkUtil);      bind(GroupUtil.class).toInstance(groupUtil);  }  //elided: getters allowing test methods to access the mocks}

Using this module requires a programmatic override of the existing Roboguice configuration. In other words, as the Application is brought to life in the Android test framework, we replace the production DI object graph with our test object graph instead. Typically you can find this in a base test class (since it’s used repeatedly), and it looks something like:

@Overrideprotected void setUp() throws Exception {    super.setUp();    Application app = (Application) getInstrumentation().getTargetContext()        .getApplicationContext();    RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,    Modules.override(RoboGuice.newDefaultRoboModule(app))        .with(new TestRoboConfigModule()));}

 

Robotium

Robotium is an Android UI testing framework that builds upon the stock Android testing framework. Add it to your project via

androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.2.1'

 

Solo

The majority of a dev’s interaction with this library is through the Solo class. Although one can instantiate a Solo object whenever convenient, frequently they are created during a test’s setUp() method since all of the test methods in that class will use it:

protected Solo solo;@Overrideprotected void setUp() throws Exception {  super.setUp();  solo = new Solo(getInstrumentation());}

 

Some of the most commonly used Solo methods include:

  • waitForActivity()
  • waitForFragmentByTag()
  • waitForText()
  • clickOnButton()
  • clickOnText()
  • enterText()
  • getString()
  • getView()

 

Robotium Best Practices

assertTrue on wait-for conditions

The waitForXyz() family of methods return true or false depending on whether their condition succeeded within the timeout period. One should always wrap them in anassertTrue() call so that the test fails if the condition was not met:

assertTrue( solo.waitForText("jabberwocky") );

waitForText()

It’s hard to go wrong with waitForText(). There are occasionally gotchas from some other methods, like waitForFragmentByTag(), since this method will return true if the fragment is in the fragment backstack (not necessarily visibile). Make sure you understand which wait-for method makes sense for your scenario, and when in doubt, use waitForText().

Use getString() to get localized String resources

Rather than looking for literal strings on the screen, one probably should be using the localized resources from strings.xml:

//a little fragilesolo.getButton("Login");//much bettersolo.getButton( solo.getString(R.string.login) );//equivalent, but unnecessarily longsolo.getButton( getInstrumention().getTargetContext()    .getResources().getString(R.string.login) );

Toasts

Use care when writing tests that depend on seeing a Toast message appear. Toasts are transient by nature, and this can lead to particularly frustrating race conditions in your tests: tests may pass on some machines (or on some test runs) but fail on others based on CPU power, number of processors, CPU load, etc. Remember that the testing thread and the UI thread are different, so write tests that look for non-transient conditions. This might mean resorting to something as simple as using waitForLogMessage() to match a corresponding log entry written to accompany a Toast.

 

Check out our previous posts on Android testing!

Android Unit Testing Concepts
JUnit Android Testing 

 

 

 

 

Ron Unger
Ron Unger