Check out our Bootcamp Schedule View Schedule

All in Together: Android Studio, Gradle and Robolectric

Jason Atwood

UPDATE: This post has been superseded by a newer version here.

In May of 2014, we made the switch to Android Studio as our preferred Android IDE, migrating away from Eclipse in our Android bootcamps and book.

As we have started new app development projects for clients, we transitioned away from IntelliJ, our previous IDE of choice. This shift has allowed us to build a uniform skillset, enhancing both our teaching and consulting. However, we still had trouble integrating some of the tools and techniques that made our past projects so successful. Android Studio recently moved out of beta, so now is a good time to share about our successes so far.

In our consulting projects, we’ve benefited from building a solid set of unit tests with Robolectric, for a few reasons:

  • Configuring Robolectric within IntelliJ + Maven allowed us to easily run tests from within the IDE, get immediate visual feedback, run subsets of tests and debug and set breakpoints within tests.
  • Having our testing tool run seamlessly within our IDE allowed us very fast turnaround on a red/green/refactor workflow.
  • We wanted to continue to rely on Robolectric as we moved to Android Studio, and as a consequence, the Gradle build system. We are already building expertise with Gradle and will continue using these skills.

We’ve spent a lot of brainpower thinking about different design architectures and patterns. All of the many patterns we discuss rely on implementing some separation of concerns. While we haven’t come to a decision about which architecture is best for us—and this is always subject to change—we realized that Android Studio modules would help us achieve this separation.

We’ve put together a set of best practices for setting up a project that uses Android Studio, Gradle and Robolectric.

Android Studio Modules

Our consulting projects are usually architected in multiple layers, with the model layer coded in pure Java. This means that there’s no dependency on the Android framework. Breaking this layer out into its own Android Studio module provides us with several benefits:

  • We can test this module outside of Robolectric, relying on Junit tests alone.
  • Breaking the model out into its own module allows us to reuse it across different projects and platforms.
  • Android Mobile, Android TV and Android Wear apps can all depend on this model layer module.
  • Multiple projects for the same client, which rely on the same backing data, can reuse this module as well.

Setting up a Java module in Android Studio is straightforward. Under “Project Structure,” click “+” and then select “Java Library.” Android Studio will generate the folder structure for this module and provide us with a separate build.gradle file. For this “core” module, the build.gradle file is relatively simple:

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.+'
}

To include the core module in our app module, we add it as a local dependency:

apply plugin: 'com.android.application'

android {...}

dependencies {
    compile project(':core')
}

One caveat worth mentioning: Any test classes of the core module in the /core/src/test directory will not be compiled into the .jar, which satisfies the dependency in the app module. If we need the core module to provide any test helpers or test doubles, we will have to add them as a separate dependency.

Robolectric

Robolectric and Android Studio/Gradle do not work together out of the box. We found two different solutions to integrate them. Both solutions work, but we found one to be superior.

Robolectic’s Gradle plugin

The first solution is to use Robolectric’s provided Gradle plugin. However, there is a drawback: Robolectric and Android Studio each utilize a different version of Junit. This causes a mismatch when compiling:

!!! JUnit version 3.8 or later expected:
    java.lang.RuntimeException: Stub!
      at junit.runner.BaseTestRunner.<init>(BaseTestRunner.java:5)
      at junit.textui.TestRunner.<init>(TestRunner.java:54)
      at junit.textui.TestRunner.<init>(TestRunner.java:48)
      at junit.textui.TestRunner.<init>(TestRunner.java:41)

Android Studio builds the dependency .iml file for us, so there is no way to prioritize these dependencies by manually setting the order of the entries. The Robolectric documentation says:

Android Studio aggressively re-writes your dependencies list (your .iml file) and subverts the technique used above to get the Android SDK to the bottom of the classpath. You will get the dreaded Stub! exception every time you re-open the project (and possibly more often). For this reason we currently recommend IntelliJ; we hope this can be solved in the future.

This hangup doesn’t entirely prevent us from using Robolectric with Gradle. We just lose the ability to rely on the IDE to speed up running tests and debugging. We are forced to use Gradle from the command line. We get very poor information on failing tests. Furthermore, we lose the ability to debug tests and set breakpoints.

JC&K Solutions Gradle plugin

Luckily, another solution exists. JC&K Solutions has written a Gradle plugin to integrate with Robolectric, and Evan Tatarka has written an Android Studio plugin to integrate the JC & K Gradle plugin with Android Studio. This solution allows us to run Robolectric unit tests within Android Studio.

To start, we need to include the JC&K Gradle plugin to our root build.gradle file:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.0.0'
        classpath 'com.github.jcandksolutions.gradle:android-unit-test:2.1.1'
    }
}
allprojects {
    repositories {
        jcenter()
    }
}

Next, we need to apply the Gradle plugin in our app’s build.gradle file:

apply plugin: 'com.android.application'

android {...}

apply plugin: 'android-unit-test'

dependencies {
    // core android studio module
    compile project(':core')

    // testing
    testCompile 'org.robolectric:robolectric:2.4'
    testCompile 'junit:junit:4.+'
}

Then we need to install the Android Studio plugin. From within Android Studio, navigate to Settings -> Plugins -> Browse Repositories… and search for “Android Studio Unit Test.”

Now is a good time to talk about some Gradle parameters that have been mismatched or left unexplained in other tutorials I’ve seen. The directory where our tests live is called a source set. When we create a new project in Android Studio, the IDE defaults to naming this directory androidTest. See the user guide for more details. However, the Android Studio Unit Test plugin will be looking for our tests in the test source set. To address this, we could choose to create an alias to remap this source set by adding to our app’s build.gradle file:

apply plugin: 'com.android.application'

android {...}

apply plugin: 'android-unit-test'

sourceSets {
    androidTest.setRoot('src/test')
}

dependencies {...}

However, it is easier, and clearer, to instead rename the androidTest source set (i.e., rename the app/src/androidTest directory to app/src/test).

I have also seen several mismatches when naming the Gradle dependency configurations inside the dependencies block. The Java Gradle plugin is looking for a testCompile configuration. All test-related dependencies should be applied to that configuration. There is no need for additional configuration dependencies for androidTestCompile or instrumentTestCompile.

apply plugin: 'com.android.application'

android {...}

apply plugin: 'android-unit-test'

dependencies {
    ...

    // testing
    testCompile 'org.robolectric:robolectric:2.4'
    testCompile 'junit:junit:4.+'

    // these aren’t getting used
    androidTestCompile 'some.other.library'
    instrumentTestCompile 'additional.library'
}

As of Android Studio 0.8.9, the test classes are no longer compiled as part of the assembleDebug task. We have to manually compile them by setting a task dependency. We accomplish this by adding to our app’s build.gradle file:

apply plugin: 'com.android.application'

android {...}

apply plugin: 'android-unit-test'

afterEvaluate {
    tasks.findByName("assembleDebug").dependsOn("testDebugClasses")
}

dependencies {...}

For more information, check out this GitHub issue.

The last thing to do is configure Robolectric to properly locate our app’s AndroidManifest.xml file. This linking doesn’t happen by default. There are two ways to set this up. The first way is to annotate each test class, hard-linking the test to the manifest:

@RunWith(RobolectricTestRunner.class)
@Config(manifest="./src/main/AndroidManifest.xml")
public class MyActivityTest {
	...
}

The second option is to subclass RobolectricTestRunner to point to the manifest.

public class CustomTestRunner extends RobolectricTestRunner {

    public CustomTestRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {
        String appRoot = "../path/to/app/src/main/";
        String manifestPath = appRoot + "AndroidManifest.xml";
        String resDir = appRoot + "res";
        String assetsDir = appRoot + "assets";
        AndroidManifest manifest = createAppManifest(Fs.fileFromPath(manifestPath),
        		Fs.fileFromPath(resDir),
        		Fs.fileFromPath(assetsDir));

        manifest.setPackageName("com.my.package.name");
        // Robolectric is already going to look in the  'app' dir ...
        // so no need to add to package name
        return manifest;
    }
}

And then use this CustomTestRunner for each test class. You’ll note that because we use a custom test runner, we have to manually tell Robolectric which API to emulate. We are currently limited to API levels 16, 17 or 18.

@Config(emulateSdk = 18)
@RunWith(CustomTestRunner.class)
public class MyActivityTest {
	...
}

I will leave it up to you to decide which of these methods is preferable.

Things are getting good, and looking better now. We have multiple Android Studio modules compiled into a single project. We have Gradle building our entire project for us. We have our Robolectric unit tests running smoothly, all within the IDE. Now it’s time to get down to business and build another sweet Android application!

I’d like to thank Josh Skeen and Ross Hambrick for their help with this post. Check out Josh’s sample repository for setting up Gradle + Android Studio + Robolectric.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project