5

whenever i run a unit test, i get this error when it tries to retrieve a string value from a custom config.xml file.

Background:

The project itself is a library apk project that uses and references another library APK project.

The error occurs when the project itself tries to initiate a new object that is a subclass of a super class contained on the referenced library apk project.

The Issue explained more below

The specific line of code that fails with the error is a static variable defined below:

  protected static final String ANDROID_CONFIG_ID =  LibraryApplication.getApplication().getString(R.string.api_checkout_config_id_override);

it fails with the following error:

java.lang.NoClassDefFoundError: com/jon/commonlib/R$string

commonLib is the referenced library apk if you are wondering.

Here is my unit test

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, manifest=Config.NONE)
public class TestSearchShows {

    @Test
    public void testSearchJsonFile(){
        EventsTestHelper testHelper = new EventsTestHelper(RuntimeEnvironment.application);

        try {
            ShowsList showList = testHelper.getShowList(new SearchEvent());

            if(showList.getShows().size() < 0){
                Assert.fail("show list is empty");
            }

        } catch (IOException e) {
            e.printStackTrace();
            Assert.fail(e.toString());
        }
    }


}

The EventsTestHelper is the sub class for a super class called NetworkHelper which looks like this:

public abstract class NetworkHelper<T, P, S> implements NetworkConstants {

protected static final String ANDROID_CONFIG_ID = LibraryApplication.getApplication().getString(R.string.api_checkout_config_id_override);

//other stuff ....

}

I use robolectric version 3.0 the latest for runing ,my unit tests.

If i was to run the code live and call and initiate the sub class, it works perfectly fine, no crashes

edit: Here is snippets of my build gradle file below

apply plugin: 'android-sdk-manager'
apply plugin: 'com.android.library'
apply plugin: 'crashlytics'


buildscript {
    repositories {
        jcenter()
        mavenCentral()
        maven { url 'http://download.crashlytics.com/maven' }
        maven { url 'http://www.testfairy.com/maven' }

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.1.0'
        classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.+'
        classpath 'com.crashlytics.tools.gradle:crashlytics-gradle:1.+'
        classpath 'com.testfairy.plugins.gradle:testfairy:1.+'

    }
}

repositories {
    mavenCentral()
    maven { url 'http://download.crashlytics.com/maven' }
    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
    flatDir {
        dirs 'libs'
    }
}

android {
    compileSdkVersion 19
    buildToolsVersion "22.0.1"
    def package_namespace = "com.jonney.moduleApp"

   defaultConfig {
        minSdkVersion 14
        testApplicationId "com.jonney.moduleApp"
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
 productFlavors {
        //testing {
        //}
        local {
        }
        mock {
        }
        qa {
        }
        //qa4 {
        //}
        production {
        }
    }

    sourceSets {



        local {
            res.srcDir 'build-config/local/res'
        }
        testing {
            res.srcDir 'build-config/testing/res'
        }
        mock {
            res.srcDir 'build-config/mock/res'
        }
        qa {
            res.srcDir 'build-config/qa/res'
        }
        qa4 {
            res.srcDir 'build-config/qa4/res'
        }
        staging {
            res.srcDir 'build-config/staging/res'
            test.java.srcDirs += 'src/main/java'
        }
        production {
            res.srcDir 'build-config/production/res'
            test.java.srcDirs += 'src/main/java'
            test.java.srcDirs += "build/generated/source/r/production"
            test.java.srcDirs += "build/generated/source/buildConfig/production"
        }

    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:19.1.+'
    compile 'com.google.code.gson:gson:2.3'
    testCompile('org.robolectric:robolectric:3.0') {
        exclude group: 'commons-logging', module: 'commons-logging'
        exclude group: 'org.apache.httpcomponents', module: 'httpclient'
    }
    compile 'com.fasterxml.jackson:jackson-parent:2.5'
    compile 'com.squareup:otto:1.3.6'
    compile 'com.jakewharton:butterknife:6.1.0'
    compile 'com.sothree.slidinguppanel:library:3.0.0'
    compile 'com.crashlytics.android:crashlytics:1.+'
    compile 'com.mcxiaoke.volley:library-aar:1.0.0'
    compile "joda-time:joda-time:2.4"
    testCompile('junit:junit:4.12') {
        exclude module: 'hamcrest'
        exclude module: 'hamcrest-core'
    }
    testCompile 'org.hamcrest:hamcrest-all:1.3'
    compile 'com.sothree.slidinguppanel:library:3.0.0'
    compile 'com.squareup:otto:1.3.6'
    compile 'com.squareup.okhttp:okhttp:2.3.0'
    testCompile 'org.apache.maven:maven-ant-tasks:2.1.3'

    compile 'com.google.android.gms:play-services:7.0.0'

    compile 'com.android.support:multidex:1.0.0'

    compile(name: 'commonLib 1.0 1', ext: 'aar')
    testCompile(name: 'commonLib-1.0 1', ext: 'aar')
}

edit: i have also tried to manually create a task that copies the r class for each dependency.

    afterEvaluate { project ->
        android.libraryVariants.each { variant ->
            // workaround for missing R class for aar dependencies
            def copyTaskName = "copy${variant.name.capitalize()}AppCompat"
            def copyTaskNameTwo = "copy${variant.name.capitalize()}commonlib"
            task(copyTaskName, type:Copy) {
                dependsOn "process${variant.name.capitalize()}Resources"
                from { "build/generated/source/r/${variant.dirName}/$package_namespace_path" }
                into { "build/generated/source/r/${variant.dirName}/com/jon/commonlib" }
//                into { "src/test/java/android/support/v7/appcompat" }
                include 'R.java'
                filter { line -> line.contains("package ${package_namespace};") ? 'package android.support.v7.appcompat;' : line }
                outputs.upToDateWhen { false }
            }
            task(copyTaskNameTwo, type:Copy) {
                dependsOn "process${variant.name.capitalize()}Resources"
                from { "build/generated/source/r/${variant.dirName}/$package_namespace_path" }
                into { "build/generated/source/r/${variant.dirName}/android/support/v7/appcompat" }
//                into { "src/test/java/android/support/v7/appcompat" }
                include 'R.java'
                filter { line -> line.contains("package ${package_namespace};") ? 'package com.jon.commonlib;' : line }
                outputs.upToDateWhen { false }
            }
            System.out.println("adding ${copyTaskName} build/generated/source/r/${variant.dirName}/$package_namespace_path ")
            println("basename =  ${variant.baseName}")
            println("directory name =  ${variant.dirName}")
            tasks.getByName("compile${variant.name.capitalize()}UnitTestJava") dependsOn copyTaskName
        }
    }

Kind regards

jonnney

Jono
  • 17,341
  • 48
  • 135
  • 217

3 Answers3

1

The project itself is a library apk project that uses and references another library APK project.

For this type of projects exist an already known issue https://github.com/robolectric/robolectric/issues/1796 but you can workaround it.

The base issue is the android/gradle behaviour for library projects which is different compared to application projects. It just ignores to generate R classes from aar dependencies.

To workaround you can provide your own R class which already contains all dependencies R values. Here an example for the appcompat dependency

afterEvaluate { project ->
  android.libraryVariants.each { variant ->
    // one line for each aar dependency
    tasks.getByName("assemble${variant.name.capitalize()}").dependsOn copyAppcompat
  }
}

// copy it for each aar dependency and adjust it to your needs
task copyAppcompat(type: Copy) {

  // replace the base package with yours (all after /r/debug/) which contains your R.class
  from 'build/generated/source/r/debug/com/example/core'.replace("/", File.separator)

  // replace the library packages with yours (all after /test/java/) with your aar dependency base package
  into 'src/test/java/android/support/v7/appcompat'.replace("/", File.separator)

  // also replace the package declaration or you will get compile errors
  filter { line -> line.contains('package com.example.core;') ? 'package android.support.v7.appcompat;' : line }
  include 'R.java'
}

An example project with your setup can be found at https://github.com/nenick/AndroidStudioAndRobolectric/tree/library-with-aar

nenick
  • 7,340
  • 3
  • 31
  • 23
  • Hi i already have that following script that fixes appCompact issues but it does not fix the issue on my actual other third party aar lib i am using – Jono Sep 01 '15 at 14:30
  • The appCompat variant is only an example how to workaround this issue. The same thing must be done for your third party lib. Copy the `task copyAppcompat` and rename it. Add new `.dependsOn copyAppcompat` line for your new copyTask. Then Adjust all paths to match your lib, for details see the comments. – nenick Sep 01 '15 at 15:18
  • am not familier with your syntax. need to have a read about gradle as your above code doesnt quite work for me. it says it cannon find property varient – Jono Sep 15 '15 at 09:37
  • ok so i fixed that error and now i see no difference and get the same failed test error saying it cant find the R class – Jono Sep 15 '15 at 10:37
  • please post your code snippets `afterEvaluate` and `task copyCommonlib` maybe there is a failure. After test run you should find the missing R class located at `src/test/java/com/jon/commonlib/` – nenick Sep 15 '15 at 11:11
  • Hiya ok i will do that, I do see the R class located in the correct location under the build/generated folder – Jono Sep 15 '15 at 11:16
  • Ok i have posted the gradle task – Jono Sep 15 '15 at 11:23
  • Current I have no idea. When you see the R class copies under the expected folders then all should work. I had some issues by copy the R classes to build/generated because they would be packed into the aar package and this results in conflicts when the library is used within an application. Please try the `src/test/java` approach and tell if the issue still exist – nenick Sep 15 '15 at 12:06
  • Here a more generic version which should copy R class for all aar dependencies https://github.com/robolectric/robolectric/issues/1796#issuecomment-145159854 – nenick Nov 08 '15 at 09:36
0

Are you including the library in your testing classpath?

 dependencies {
    androidTestCompile(<reference to commonLib>)
 }
zec
  • 294
  • 1
  • 9
  • i have it as compile() i will try your suggestion – Jono Aug 19 '15 at 15:48
  • Ok yes i have now added the aditional dependency and i stil get the same error. here is what i added compile(name: 'commonlib-production-release-1.0 1', ext: 'aar') testCompile(name: 'commonlib-production-release-1.0 1', ext: 'aar') – Jono Aug 19 '15 at 15:58
  • NoClassDefFoundError means your test project doesn't know about that supporting library. I am also assuming your build.gradle file compiled since you said you specified via `testCompile` - which tells me that you have a build type _test_ for unit testing. – zec Aug 19 '15 at 16:14
  • Yup it compiled fine when i added the testCompile dependency. – Jono Aug 19 '15 at 16:26
  • This is for a Robolectric test, androidTestCompile is for on-device testing, it wouldn't affect it whether it was needed or not. – Alex Florescu Aug 28 '15 at 10:36
0

I wasn't able to reproduce this in any way, but here are two things that might help.

  • Try using RobolectricGradleTestRunner instead of RobolectricTestRunner
  • You shouldn't need to put your other library in testCompile. So I think you can safely remove testCompile(name: 'commonLib-1.0 1', ext: 'aar')

Let me know in the comments if you have any updates after you try the different runner.

Alex Florescu
  • 5,096
  • 1
  • 28
  • 49