31

I'm trying to run my Robolectric tests together with the new Gradle Android build system, but I'm stuck at accessing the resources of my main project.

I split the build into two separate projects to avoid conflicts between the java and the android gradle plugins, so the directory structure looks roughly like this:

.
├── build.gradle
├── settings.gradle
├── mainproject
│   ├── build
│   │   ├── classes
│   │   │   └── debug
│   ├── build.gradle
│   └── src
│       └── main
│           ├── AndroidManifest.xml
│           └── ...
└── test
    ├── build.gradle
    └── src
        └── test
            └── java
                └── ...
                    └── test
                        ├── MainActivityTest.java
                        ├── Runner.java
                        ├── ServerTestCase.java
                        └── StatusFetcherTest.java

My build.gradle in test/ currently looks like this:

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.stanfy.android:gradle-plugin-java-robolectric:2.0'
    }
}

apply plugin: 'java-robolectric'

repositories {...}

javarob {
    packageName = 'com.example.mainproject'
}

test {
    dependsOn ':mainproject:build'
    scanForTestClasses = false
    include "**/*Test.class"
    // Oh, the humanity!
    def srcDir = project(':mainproject').android.sourceSets.main.java.srcDirs.toArray()[0].getAbsolutePath()
    workingDir srcDir.substring(0, srcDir.lastIndexOf('/'))
}

project(':mainproject').android.sourceSets.main.java.srcDirs.each {dir ->
    def buildDir = dir.getAbsolutePath().split('/')
    buildDir =  (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')

    sourceSets.test.compileClasspath += files(buildDir)
    sourceSets.test.runtimeClasspath += files(buildDir)
}

dependencies {    
    testCompile group: 'com.google.android', name: 'android', version: '4.1.1.4'
    testCompile group: 'org.robolectric', name: 'robolectric', version: '2.0-alpha-3'
    ...
}

The evil classpath hackery allows me to access all classes of my main project, except for R, which exists as .class file in the build directory, but raises this error during the compileTestJava task:

/.../MainActivityTest.java:16: error: cannot find symbol
                final String appName = activity.getResources().getString(R.string.app_name);
                                                                          ^
  symbol:   variable string
  location: class R
1 error
:test:compileTestJava FAILED

There must be a better way to execute Robolectric tests with the new build system, right?

(Full source of the app)

passy
  • 7,170
  • 5
  • 36
  • 38
  • 2
    I don't think this is going to work. There's some other internal stuff in robolectric about where to find the actual resources that will not work (or scale to project with dependencies) with what the gradle plugin does. I need to work with the Robolectric developers to make it compatible. It's on my list of things to do. – Xavier Ducrohet May 20 '13 at 19:25
  • So is there a way yet to run robolectric with gradle in the new Android Studio ide? – Imanol May 26 '13 at 08:58
  • @Xav is there any chance to get this working any time soon via Gradle ? Could you point to an issue on Robolectric's github pages ? – Snicolas Jul 31 '13 at 05:50

2 Answers2

27

I was running across this same issue and this is what I came up with. Instead of creating a separate project for the tests, I created a source set for the Robolectric tests and added a new task that "check" would depend on. Using some of the code from your question, here are the relevant bits of the (working) build file:

apply plugin: 'android'

sourceSets {
    testLocal {
        java.srcDir file('src/test/java')
        resources.srcDir file('src/test/resources')
    }
}

dependencies {
    compile 'org.roboguice:roboguice:2.0'
    compile 'com.google.android:support-v4:r6'

    testLocalCompile 'junit:junit:4.8.2'
    testLocalCompile 'org.robolectric:robolectric:2.1'
    testLocalCompile 'com.google.android:android:4.0.1.2'
    testLocalCompile 'com.google.android:support-v4:r6'
    testLocalCompile 'org.roboguice:roboguice:2.0'
}

task localTest(type: Test, dependsOn: assemble) {
    testClassesDir = sourceSets.testLocal.output.classesDir

    android.sourceSets.main.java.srcDirs.each { dir ->
        def buildDir = dir.getAbsolutePath().split('/')
        buildDir =  (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')

        sourceSets.testLocal.compileClasspath += files(buildDir)
        sourceSets.testLocal.runtimeClasspath += files(buildDir)
    }

    classpath = sourceSets.testLocal.runtimeClasspath
}

check.dependsOn localTest

I've included my dependencies block to point out that in order for me to get this up and going, I had to repeat all of my compile dependencies in my custom testLocal source set.

Running gradle testLocal builds and runs just the tests inside of src/test/java, while running gradle check runs these tests in addition to those in the default android instrumentTest source set.

Hope this helps!

user2457888
  • 286
  • 3
  • 3
  • This looks like a huge step into the right direction, thanks! The Robolectric testrunner is executing, however, I'm now getting a "android.content.res.Resources$NotFoundException: unknown resource xxx" when anything tries to access a resource. Full stacktrace here: https://gist.github.com/passy/255bbd42ada11ad5fba7 – passy Jun 06 '13 at 22:44
  • 1
    This seems to come from the testrunner being unable to find the `AndroidManifest.xml`, which lies in `src/main`. I guess I could set those paths in the a RobolectricTestRunner subclass, but with Eclipse I would just set the working directory to the main project. Can I do that in the gradle file? – passy Jun 06 '13 at 23:25
  • 1
    @passy Unfortunately I haven't seen that issue come up. However, you should be able to put your `AndroidManifest.xml` in the root of the project directory and override the manifest source set by doing something similar to this in `build.gradle`: `android { sourceSets { main { manifest.srcFile 'AndroidManifest.xml' } } }` Source: http://tools.android.com/tech-docs/new-build-system/user-guide – user2457888 Jun 07 '13 at 02:18
  • 3
    Thanks again, @user2457888. :) I set the `workingDir` inside the `localTest` task to `src/main` and now it finds the `AndroidManifest.xml` and all resources. – passy Jun 07 '13 at 16:32
  • I have everything working from gradle, but the IDE complains because it can't find the imports. How did you add the dependencies to the project in the ide? Did you create a module? How did you configure it? – alex Jun 15 '13 at 13:54
  • thanks - that looks good - unfortunately I get "Could not find method testLocalCompile() for arguments [junit:junit:4.8.2] on project" - any Idea on how to solve this? – ligi Jun 20 '13 at 08:58
  • I got this working, and merged with the "old" example from https://github.com/novoda/robolectric-plugin , and so was able to remove the duplicate dependency calls. I'm able to run it from the command line just fine. Getting Android Studio to see it, as alex mentioned, is a problem. I was able to set src/test/main as a root test folder - but the Studio can't find junit, robolectric, etc. test dependencies. – Mark Jul 01 '13 at 16:03
  • 1
    @alex - I figured it out! Go into the module settings, in dependencies, add a new library, choose from maven, and add robolectric. Change the scope on it to compile. Works for now, although it might interfere if you're using instrumentationTest as well. – Mark Jul 01 '13 at 19:18
  • @Mark do you mind sharing how you were able to set src/test/main as a root test folder? If I configure this via the IDE, it keeps resetting itself to a non-src dir, presumably because it's not set in my build.gradle. – ebernie Jul 05 '13 at 21:21
  • You right click on it, and mark it as test root - I forget the exact menu sequence. But you're right - it does keep resetting itself. It looks like there's no way to have it stick. – Mark Jul 10 '13 at 11:20
  • This is really cool...but I have a project that I ported from Eclipse into Android Studio and therefore has the old folder structure: (Project folder/ src, res, libs, gen ...) . How would that affect the localTest and specifically the buildDir variable? – Thomas Kaliakos Jul 15 '13 at 12:10
  • Hi user2457888, could you upload a sample project to a public repo? I'm unable to compile the test project, I could really use some real code. Also, have you tested it for Gradle 0.5.+? Thanks! – Maragues Aug 05 '13 at 12:35
  • Any idea how this could be updated to include the yet-to-be-standard aar format as a dependency? See my related issue here: http://stackoverflow.com/questions/21099754/gradle-android-unit-tests-that-depend-on-an-aar – Colin M. Jan 14 '14 at 00:18
  • Anyone have luck modifying this method to work with flavors? When I add flavors, the testLocal fails not being able to find _any_ of my classes, even though there's no classes needed from the flavors for the tests. – Colin M. Nov 20 '14 at 21:26
8

Update: Jake Wharton just announced the gradle-android-test-plugin. You can find it at https://github.com/square/gradle-android-test-plugin

It seems to be pretty streamlined, especially if you plan to use robolectric.


Old Answer Below

The robolectric-plugin looks promising.

The sample build.gradle file they provide is :

buildscript {
    repositories {
        mavenCentral()
        maven {
            url "https://oss.sonatype.org/content/repositories/snapshots"
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.4.2'
        classpath 'com.novoda.gradle:robolectric-plugin:0.0.1-SNAPSHOT'
    }
}

apply plugin: 'android'
apply plugin: 'robolectric'

repositories {
    mavenCentral()
    mavenLocal()
    maven {
        url "https://oss.sonatype.org/content/repositories/snapshots"
    }
}

dependencies {
    //compile files('libs/android-support-v4.jar')

    // had to deploy to sonatype to get AAR to work
    compile 'com.novoda:actionbarsherlock:4.3.2-SNAPSHOT'

    robolectricCompile 'org.robolectric:robolectric:2.0'
    robolectricCompile group: 'junit', name: 'junit', version: '4.+'
}

android {
    compileSdkVersion 17
    buildToolsVersion "17.0.0"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 17
    }
}

It doesn't seem to work with the Android Gradle plugin version 0.5 but maybe it will soon.

Saad Farooq
  • 13,172
  • 10
  • 68
  • 94
  • I'm anxiously awaiting Jake's plugin to support resources split by buildType. Currently it can't seem to deal with that. Someone wrote a patch to fix this (a few months ago) but there's been no comments on it and has not been accepted. Until then, the method in the accepted answer is working. – Colin M. Jan 03 '14 at 17:38
  • 2
    Seems like Jake Wharton's plugin is now deprecated? – Flame Feb 19 '14 at 04:27
  • Yes it is. There seems to be a flurry to move to the standard android test infrastructure. Robolectric is still helpful for some cases but I found it easier to manage the application so that most of my pure java classes are java library modules that are unit tested with junit and Android specific ones with the android test framework. – Saad Farooq Feb 19 '14 at 17:24
  • Both old and new answer reference deprecated plugins – Cris Sep 19 '18 at 08:18