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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With