- Android Studio (tested with 0.8.10)
- Gradle or Gradle Wrapper (tested with 1.12)
- Robolectric Gradle Plugin (tested with 2.3)
- The Android SDK with API Level 18 installed
Setup a new Android project with Android Studio. If you already have done this you can skip this step.
You find a description of the process here. From this point I'm assuming the following project structure:
robo
|- app
| |- build
| |- libs
| |- src
| |- build.gradle
|- build.gradle
|- settings.gradle
/* ommitted some files due to laziness */
If not noted elsewise every path in this document will be relative to robo
Add the robolectric classpath to build.gradle
:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:0.12.2"
classpath "org.robolectric:robolectric-gradle-plugin:0.11.+" // robolectric classpath
}
}
allprojects {
repositories {
jcenter()
}
}
Add Robolectric dependencies to app/build.gradle
:
apply plugin: "com.android.application"
apply plugin: "robolectric" // load robolectric plugin
android {
compileSdkVersion 20
buildToolsVersion "20.0.0"
defaultConfig {
applicationId "de.neusta.ms.avbk.example.robo"
minSdkVersion 14
targetSdkVersion 20
versionCode 1
versionName "1.0"
}
buildTypes {
debug {
debuggable true
}
release {
runProguard false
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
// setup robolectric to use only files below a folder with name "robolectric"
robolectric {
include "**/robolectric/**/*Test.class"
}
dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
// use robolectric when testing
androidTestCompile("org.robolectric:robolectric:2.3") {
exclude module: "commons-logging"
exclude module: "httpclient"
}
androidTestCompile "junit:junit:4.11" // use junit 4 for testing
}
Create a new directory for your Robolectric Tests in app/src/androidTest/java/
according to your applicationId. So for this example, where the applicationId is de.neusta.ms.avbk.example.robo
, I put my Robolectric test files to app/src/androidTest/java/de/neusta/ms/avbk/example/robo/tests/robolectric/
.
Create a first Robolectric test in app/src/androidTest/java/de/neusta/ms/avbk/example/robo/tests/robolectric/SampleTest.java
:
package de.neusta.ms.avbk.example.robo.tests.robolectric;
import android.app.Activity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import de.neusta.ms.avbk.example.robo.MyActivity;
import de.neusta.ms.avbk.example.robo.R;
import static org.junit.Assert.assertNotNull;
@RunWith(RobolectricTestRunner.class)
@Config(emulateSdk = 18)
public class SampleTest {
@Test
public void testRobolectricWorks() throws Exception {
Activity activity = Robolectric.buildActivity(MyActivity.class).create().get();
assertNotNull(activity);
assertNotNull(activity.findViewById(R.id.example_textview));
}
}
Open up a terminal (or command line for those who have to use Windows), navigate to the app module of your project and run gradle
avbk@alex-laptop ~ % cd projects/robo/app
avbk@alex-laptop ~/projects/robo/app % gradle test
Creating properties on demand (a.k.a. dynamic properties) has been deprecated and is scheduled to be removed in Gradle 2.0. Please read http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html for information on the replacement for dynamic properties.
Deprecated dynamic property: "destinationDir" on "task ':app:testDebugClasses'", value: "/home/avbk/projects/ro...".
:app:preBuild
:app:preDebugBuild
:app:checkDebugManifest
:app:prepareDebugDependencies
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:generateDebugAssets UP-TO-DATE
:app:mergeDebugAssets UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:processDebugManifest UP-TO-DATE
:app:processDebugResources UP-TO-DATE
:app:generateDebugSources UP-TO-DATE
:app:compileDebugJava UP-TO-DATE
:app:compileTestDebugJava
:app:processTestDebugResources UP-TO-DATE
:app:testDebugClasses
:app:testDebug
:app:test
BUILD SUCCESSFUL
Total time: 7.51 secs
If anything fails at this point, check Google and StackOverflow for help ;-)
Edit your app/build.gradle
to add a special task to include build/test-classes
into the classpath if needded:
/** omitted lines for better readability **/
dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
androidTestCompile("org.robolectric:robolectric:2.3") {
exclude module: "commons-logging"
exclude module: "httpclient"
}
androidTestCompile "junit:junit:4.11"
}
// tell gradle to include robolectric test classes
task copyTestClasses(type: Copy) {
from "build/test-classes"
into "build/intermediates/classes/debug"
}
(if you want to test a different flavour or buildtype, change /debug
in this path accordingly)
In Android Studio you need to add a new Run-Configuration.
- Click on
Run -> Edit Configurations
- In the dialog press the
+
Symbol and selectGradle
- Setup the configuration as to following:
- Gradle Project:
:robo:app
- Tasks:
testClasses copyTestClasses
Save this configuration by pressing OK.
In Android Studio you need to add a new Run-Configuration.
- Click on
Run -> Edit Configurations
- In the dialog press the
+
Symbol and selectJUnit
- Setup the configuration as to following:
- Name:
Robolectric
- Test kind:
All in package
- Package:
de.neusta.ms.avbk.example.robo.tests.robolectric
- Search for tests:
In single module
- VM Options:
-ea -Dandroid.assets=build/intermediates/assets/debug -Dandroid.manifest=build/intermediates/manifests/debug/AndroidManifest.xml -Dandroid.resources=build/intermediates/res/debug
(if you want to test a different flavour or buildtype, changedebug
in those paths accordingly) - Working Directory:
$MODULE_DIR$
- Use classpath of module:
app
- Check
Use alternate JRE
and select your default Java VM, e.g./usr/lib/jvm/java-1.7.0-openjdk-i386
- Under
Before Launch
press+
and addBuild test classes
by selectingRun Another Configuration
Save this configuration by pressing OK.
It should look like this screenshot:
This configuration will compile and run Robolectric with your default JVM and setup the assets, resources and manifest correctly. But since it will run on the wrong JVM, none of the Android Classes are found, which leads to the following error message:
java.lang.NoClassDefFoundError: android/R
at org.robolectric.bytecode.Setup.<clinit>(Setup.java:39)
at org.robolectric.RobolectricTestRunner.createSetup(RobolectricTestRunner.java:137)
at org.robolectric.RobolectricTestRunner.createSdkEnvironment(RobolectricTestRunner.java:114)
...
Obviously you need to include the android.jar
, but if you would do so you will fail with the famous stub-exception:
!!! 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)
...
This is due to the fact that android has its own version of JUnit included (as stubs) and Android Studio will select its TestRunner
instead of the TestRunner
of Robolectric. I read about hacks to reorder your *.iml so that Robolectric's TestRunner
will be selected by default. But I don't like this approach, since the *.iml files will be recreated whenever Android Studio syncs the gradle files.
So I came up with another solution that might look even more like an evil hack but imho is more robust.
The basic idea is to use the android.jar
of API-Level 18 without its JUnit capabilities. This can be achieved quite easily.
Open up a terminal (sorry Windows-Users, you have to figure out a more GUI-ish way I guess ;-) ) and enter the following:
avbk@alex-laptop ~ % cd projects/robo/app
avbk@alex-laptop ~/projects/robo/app % mkdir testlibs
avbk@alex-laptop ~/projects/robo/app % cp $ANDROID_HOME/platforms/android-18/android.jar testlibs/android-18-stripped.jar
avbk@alex-laptop ~/projects/robo/app % zip --delete testlibs/android-18-stripped.jar junit*
deleting: junit/
deleting: junit/framework/
deleting: junit/framework/TestCase.class
deleting: junit/framework/Test.class
deleting: junit/framework/AssertionFailedError.class
deleting: junit/framework/ComparisonFailure.class
deleting: junit/framework/TestFailure.class
deleting: junit/framework/Assert.class
deleting: junit/framework/TestSuite.class
deleting: junit/framework/TestListener.class
deleting: junit/framework/Protectable.class
deleting: junit/framework/TestResult.class
deleting: junit/runner/
deleting: junit/runner/Version.class
deleting: junit/runner/TestSuiteLoader.class
deleting: junit/runner/BaseTestRunner.class
(note that $ANDROID_HOME
points to the installation path of my Android SDK)
After JUnit is removed from the jar, you only need to tell gradle to use it when testing. Edit your app/build.gradle
:
/** omitted lines for better readability **/
dependencies {
compile fileTree(dir: "libs", include: ["*.jar"])
androidTestCompile("org.robolectric:robolectric:2.3") {
exclude module: "commons-logging"
exclude module: "httpclient"
}
androidTestCompile "junit:junit:4.11"
// add the stripped version of android.jar
androidTestCompile files("testlibs/android-18-stripped.jar")
}
task copyTestClasses(type: Copy) {
from "build/test-classes"
into "build/intermediates/classes/debug"
}
Now you should be able to run and debug your tests from Android Studio by running the Robolectric
-Configuration (infrequently this method fails to start up JUnit correctly and ends with an exception of a missing JUnit file. But on the second run it worked for me TM ;-) )
Got inspired by http://blog.blundell-apps.com/how-to-run-robolectric-junit-tests-in-android-studio/