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






