The problem with the current plugin is that it does not escape the File.Seperator correctly, and although a merge-request is pending on their Git repository, they haven't fixed this to a new stable or snapshot release.
The code of the current (0.0.1 - SNAPSHOT) currently states:
def buildDir = dir.getAbsolutePath().split(File.separator)Where it should be
def buildDir = dir.getAbsolutePath().split(Pattern.quote(File.separator))
Fortuneatly enough, the source code is publicly available as a Groovy based plugin. For ease-of-use (and my lack of understanding on how to compile a plugin :) ), I've posted my Gradle configuration below.
Next to having your normal buildscript and android configuration, you will need to :
- Import two additional classes, being Callable and Pattern
- Define the plugin: robolectric
- Paste-in the code that starts with "class robolectric implements Plugin
"
Note
With the release of Android Studio 0.5.0, some upgrading of your Gradle build file is required (using the Gradle plugin version 0.9.+). In the dependencies change the instrumentTestCompile command to androidTestCompile. If you'll have exceptions during the Project Gradle Sync, make sure to restart your IDE and invalidate the caches (File > Invalidate Caches/Restart). I've update the build file in the provided examples.
The code
//--- import additional classes --- import java.util.concurrent.Callable import java.util.regex.Pattern //--- end import --- buildscript { repositories { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } } dependencies { classpath 'com.android.tools.build:gradle:0.9.+' classpath 'com.novoda.gradle:robolectric-plugin:0.0.1-SNAPSHOT' } } apply plugin: 'android' //--- use the new plugin robolectric, which is the classname of the plugin apply plugin: robolectric android { compileSdkVersion 19 buildToolsVersion '19.0.1' defaultConfig { minSdkVersion 15 targetSdkVersion 19 versionCode 1 versionName "1.0" } sourceSets { //the tests are still stored in /src/test instrumentTest.setRoot('src/test') } buildTypes { } } dependencies { compile 'com.android.support:appcompat-v7:+' androidTestCompile 'org.robolectric:robolectric:2.+' androidTestCompile 'junit:junit:4.+' } //ignore classes that do not have the extension Test in their classname tasks.withType(Test) { scanForTestClasses = false include "**/*Test.class" } /** * ----------------------------------------------------------- * ROBOLECTRIC PLUGIN CLAS * ----------------------------------------------------------- */ class robolectric implements Plugin<Project> { public static final String ANDROID_PLUGIN_NAME = "android"; public static final String ANDROID_LIBRARY_PLUGIN_NAME = "android-library"; public static final String COMPILE_CONFIGURATION_NAME = "compile"; public static final String TEST_COMPILE_CONFIGURATION_NAME = "robolectricTestCompile"; public static final String RUNTIME_CONFIGURATION_NAME = "runtime"; public static final String TEST_RUNTIME_CONFIGURATION_NAME = "robolectricTestRuntime"; public static final String ROBOLECTRIC_SOURCE_SET_NAME = "robolectric"; public static final String ROBOLECTRIC_CONFIGURATION_NAME = "robolectric"; public static final String ROBOLECTRIC_TASK_NAME = "robolectric"; void apply(Project project) { project.getPlugins().apply(JavaBasePlugin.class); ensureValidProject(project); JavaPluginConvention javaConvention = project.getConvention().getPlugin(JavaPluginConvention.class); configureConfigurations(project); configureSourceSets(javaConvention); configureTest(project, javaConvention); project.afterEvaluate { configureAndroidDependency(project, javaConvention) } } def configureAndroidDependency(Project project, JavaPluginConvention pluginConvention) { SourceSet robolectric = pluginConvention.getSourceSets().findByName(ROBOLECTRIC_SOURCE_SET_NAME); (getAndroidPlugin(project)).mainSourceSet.java.srcDirs.each { dir -> //Fault: def buildDir = dir.getAbsolutePath().split(File.separator) def buildDir = dir.getAbsolutePath().split(Pattern.quote(File.separator)) buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join(File.separator) robolectric.compileClasspath += project.files(buildDir) robolectric.runtimeClasspath += project.files(buildDir) } getAndroidPlugin(project).variantDataList.each { it.variantDependency.getJarDependencies().each { robolectric.compileClasspath += project.files(it.jarFile) robolectric.runtimeClasspath += project.files(it.jarFile) } } // AAR files getAndroidPlugin(project).prepareTaskMap.each { robolectric.compileClasspath += project.fileTree(dir: it.value.explodedDir, include: '*.jar') robolectric.runtimeClasspath += project.fileTree(dir: it.value.explodedDir, include: '*.jar') } // Default Android jar getAndroidPlugin(project).getRuntimeJarList().each { robolectric.compileClasspath += project.files(it) robolectric.runtimeClasspath += project.files(it) } robolectric.runtimeClasspath = robolectric.runtimeClasspath.filter { it true } } private void ensureValidProject(Project project) { boolean isAndroidProject = project.getPlugins().hasPlugin(ANDROID_PLUGIN_NAME); boolean isAndroidLibProject = project.getPlugins().hasPlugin(ANDROID_LIBRARY_PLUGIN_NAME); if (!(isAndroidLibProject | isAndroidProject)) { throw new RuntimeException("Not a valid Android project"); } } void configureConfigurations(Project project) { ConfigurationContainer configurations = project.getConfigurations(); Configuration compileConfiguration = configurations.getByName(COMPILE_CONFIGURATION_NAME); Configuration robolectric = configurations.create(ROBOLECTRIC_CONFIGURATION_NAME); robolectric.extendsFrom(compileConfiguration); } private void configureSourceSets(final JavaPluginConvention pluginConvention) { final Project project = pluginConvention.getProject(); SourceSet robolectric = pluginConvention.getSourceSets().create(ROBOLECTRIC_SOURCE_SET_NAME); robolectric.java.srcDir project.file('src/test/java') robolectric.compileClasspath += project.configurations.robolectric robolectric.runtimeClasspath += robolectric.compileClasspath } private void configureTest(final Project project, final JavaPluginConvention pluginConvention) { project.getTasks().withType(Test.class, new Action<Test>() { public void execute(final Test test) { test.workingDir 'src/main' test.getConventionMapping().map("testClassesDir", new Callable<Object>() { public Object call() throws Exception { return pluginConvention.getSourceSets().getByName("robolectric").getOutput().getClassesDir(); } }); test.getConventionMapping().map("classpath", new Callable<Object>() { public Object call() throws Exception { return pluginConvention.getSourceSets().getByName("robolectric").getRuntimeClasspath(); } }); test.getConventionMapping().map("testSrcDirs", new Callable<Object>() { public Object call() throws Exception { return new ArrayList<File>(pluginConvention.getSourceSets().getByName("robolectric").getJava().getSrcDirs()); } }); } }); Test test = project.getTasks().create(ROBOLECTRIC_TASK_NAME, Test.class); project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(test); test.setDescription("Runs the unit tests using robolectric."); test.setGroup(JavaBasePlugin.VERIFICATION_GROUP); test.dependsOn(project.getTasks().findByName('robolectricClasses')) test.dependsOn(project.getTasks().findByName('assemble')) } private Plugin getAndroidPlugin(Project project) { if (project.getPlugins().hasPlugin(ANDROID_LIBRARY_PLUGIN_NAME)) { return project.getPlugins().findPlugin(ANDROID_LIBRARY_PLUGIN_NAME); } return project.getPlugins().findPlugin(ANDROID_PLUGIN_NAME); } } //--- END CLASS DEFINITION --