Robolectric not finding resources for flavors with different app IDs - android

In Android Studio, I am writing unit tests with Robolectric for multiple flavors, and it seems that I can only access the resources from the tests of the flavor with the default application ID.
My test directory structure is:
-src
...
- test (these tests will run on all flavors)
- java
- com.example.main
- UnitTest.java
- testDefault (these tests will only run on flavor "main")
- java
- com.example.main
- DefaultUnitTest.java
- testOtherFlavor (these tests will only run on flavor "otherFlavor")
- java
- com.example.otherflavor
- OtherFlavorUnitTest.java
- main
...
- res
- values
- strings.xml
- otherflavor
- res
- values
- strings.xml
In my build.gradle I have
productFlavors {
default{}
otherFlavor {
applicationId "com.example.otherflavor"
versionNameSuffix "-otherFlavor"
}
}
My test is the following:
package com.example.otherflavor;
import android.context.Context;
import com.example.main.R;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
#RunWith(RobolectricTestRunner.class)
#Config(packageName = com.example.otherflavor)
public class OtherFlavorUnitTest {
private Context contextMock;
#Test
public void test() {
contextMock = RuntimeEnvironment.application.getApplicationContext();
System.out.println(contextMock.getString(R.string.someString));
}
}
someString is located in both strings.xml files with different values, so that when running the flavor "otherflavor" the value of its strings.xml overrides that of the default flavor. The problem is that when I run this code in the DefaultUnitTest.java (with the package names accordingly changed) the test passes, however when I run this code in the OtherFlavorUnitTest.java I get:
android.content.res.Resources$NotFoundException: Unable to find resource ID #0x7f0e0059 in packages [com.example.otherflavor, android]
However if I remove "applicationId "com.example.otherflavor"" from the build.gradle the tests pass (and the resource retrieved by getString() is indeed the resource of otherFlavor not of the default flavor).
Does someone know why Robolectric cannot find resources when the flavor has a different App ID?

The short answer is that test must be flexible enough to run its tests no matter what the applicationId is set to. When done correctly, you select the flavor from the Build Variant window. Then the tests run against the currently selected flavor. Alternatively, you can run ./gradlew testFlavor or ./gradlew testDefault from the command line.

You can create a class to be a provider of the resource in each flavor test directory so each file will have a import matching the aplicationId like com.example.myapp.flavorOne.test.R or flavorTwo.test.R
and then from test class that would be on androidTest(the main test directory) you can get the resource from the resource provider you created matching the build variant (flavor) you are

Related

Redeclaration error with same classname in different Source Sets

Similar to flavor variants, I was expecting the ability to have source set variants for the same classname. For example,
androidTest directory
class RobotTest(val name: String = "androidTest")
main directory
class RobotTest(val name: String = "main")
test directory
class RobotTest(val name: String = "test")
Android Studio is okay when I have the same classname in just 2 of the sources:
main and test
main and androidTest
However, once I add the same class to all three, Android Studio shows an Analysis error in test and androidTest with the message:
Redeclaration: RobotTest
I ran the code in a unit test, instrumented test, and in the app and everything compiled fine (no build errors) and when logging RobotTest().name, I correctly get the 3 different values for each class context.
Does anyone have any idea how to fix this or if this isn't allowed?
When you have created a product flavor you should not place your varianted class into main source set.
Keep only common for all variants files into main
For example, you defined:
flavorDimensions("api")
productFlavors {
create("real") {
dimension = "api"
}
create("mock") {
dimension = "api"
}
}
Since that, you have exactly TWO build variants: real OR mock.
So, you have to have exactly TWO versions of your class (if it should be different):
/src/real/kotlin/com/example/SomeApi.kt
/src/main/kotlin/com/example/ (no file here)
/src/mock/kotlin/com/example/SomeApi.kt
UPD:
All above is correct only for sources! If you create 4 flavors, you must copy your Java/Kotlin class into 4 folders, you don't have a 'default' one.
For resources, the logic is different.
Gradle, at first, takes the resource file (some XML file) from the default folder 'main' and then replaces it with the overridden one from the flavor folder if the file exists there. If you create 4 flavors, you can keep a default in 'main' and you can override it only once.

BuildConfigField R.style.AKTheme is not accessible in androidTest BuildConfig.java class

My project has different build types i.e. debug, beta and production and also have different product flavors i.e. QA and Integration. I have defined
a
buildConfigField 'int', 'APP_THEME', 'R.style.AKTheme'
in the productFlavors to have a separate theme for each flavor. The generated BuildConfig.java for app source set have the APP_THEME field and it is working as expected.
Recently I have started writing instrumentation tests for my app. When I try to run these tests Android studio gives me the error that can not resolve AKTheme i.e.
final int APP_THEME = R.style.AKTheme in the generated BuildConfig.java for the test source set.
It seems that R.style.AKTheme is not accessible to the generated BuildConfig.java file (test source set). I searched over internet but didn't find any help.
R.style.AKTheme is a reference, not a value, while in BuildConfig you can only use values.
There are couple of ways to achieve what you want:
Use the String name of the style in BuildConfig:
buildConfigField 'String', 'APP_THEME', '"AKTheme"'
and then in code to get the style res id:
int style = context.getResources().getIdentifier(BuildConfig.APP_THEME, "style", context.getPackageName());
Now you can use style.
You can use different source-sets.
If you are using different buildtypes, you can create a directory for that build type, and put any different resources specially for that build type in that directory. The directory should be created in the same directory as main sources directory, and named exactly the same as the buildType. Details: https://developer.android.com/studio/build/build-variants
I had quite the same issue. I had this in my build.gradle to get the right ssl certificate depending on the env. :
buildConfigField 'int', 'SSL_CERTIFICAT_RAWRES_', 'R.raw.devcert'
This was working to build and run the project, but I faced an issue when I wanted to run the task : "compileDebugAndroidTestJavaWithJavac" (used for sonarqube in my case). The compiler didn't found the resource file R when he automatically generates the buildConfig file.
The solution was to put a String to identify my certificate file "devcert" in the build.gradle instead of using directly the resInt "R.raw" :
buildConfigField 'String', 'SSL_CERTIFICAT_RAWRES_STRING', '"devcert"'
and then in my code, I get the raw file certificate like this :
final int raw = context.getResources().getIdentifier(BuildConfig.SSL_CERTIFICAT_RAWRES_STRING, "raw", context.getPackageName());
That way, the generated buildConfig found correctly the String identifier to retrieve the wanted raw file using the code above.
I found my answer here: https://developer.android.com/studio/test#create_instrumented_test_for_a_build_variant
For my case my test project and my main project is referencing a different package name, e.g. at the top of BuildConfig.java one referencing to package 'com.xxx.xxx' while one is referencing to 'com.xxx.xxx.test'
adding the line
testApplicationId = "com.xxx.xxx"
in defaultConfig in app level build.gradle file solves the issue for me

Robolectric cannot find AndroidManifest.xml

This test originally ran fine. Checked out a new branch several days later (with commits from many other developers) and it no longer works.
Test class in the mylibrary library module:
import com.company.mylibrary.BuildConfig;
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, manifest = "src/main/AndroidManifest.xml", sdk = 21)
public class MyTest {
I have also tried:
#Config(constants = BuildConfig.class, sdk = 21)
#Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21)
In the library module's build.gradle
dependencies {
.
.
testCompile 'org.robolectric:robolectric:3.0'
Error message when running inside AS is:
java.lang.RuntimeException: build/intermediates/manifests/full/debug/AndroidManifest.xml not found or not a file; it should point to your project's AndroidManifest.xml
Error message when running from command line is:
com.company.mylibrary.framework1.feature1.MyTest > testMethod STANDARD_ERROR
java.lang.RuntimeException: build/intermediates/manifests/full/debug/AndroidManifest.xml not found or not a file; it should point to your project's AndroidManifest.xml
A) Don't know why it is looking there for the manifest
B) That file/directory does not exist
C) src/main/AndroidManifest.xml does exist
Things I have tried:
- deleted the build directory in that library module
- restarted Android Studio
- Build/Clean
- Build/Rebuild Project
- run the test (both inside AS and from command line)
- and tried different versions of the #Config notation
Seems to be in a wonky state that I cannot clear.
I am working on a MacBook Pro. Android Studio 2.0 beta5
You need to set the working directory within the test's run configuration to the module directory.
Well, I've tackled the issue you're facing right now several times and found solution suitable for myself.
Generally, if your test logic does not require access to the application's resources, it's worth using usual RobolectricTestRunner as the time of the test execution is relatively shorter comparing it to the test execution time under RobolectricGradleTestRunner.
If, for some reason, you need access to the specific AndroidManifest.xml file, IMO it's better to come up with test file rather than to operate on the project's one.
By saying 'test file' I mean the following:
Let's start by defining what are the methods that can help us to obtain path to the resources files. The goal is to be able execute tests under Android Studio and, what's more relevant, via CLI (gradle :project:testBuildTypeUnitTest)
Java's System class: System.getProperty('user.dir') returns User's current working directory. Obtaining current directory we are in may help us to obtain paths to the resources we need to run our test having them provided.
Overriding RobolectricGradleTestRunner. To create our customized test runner we need the AndroidManifest.xml, the res directory and the assets directory paths:
public class CompassApplicationRobolectricTestRunner extends RobolectricGradleTestRunner {
private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.LOLLIPOP;
private static final int MIN_SDK_VERSION = Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
public CompassApplicationRobolectricTestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
#Override
protected AndroidManifest getAppManifest(Config config) {
final String manifestPath = PathResolver.resolveAndroidManifestPath();
final String resourcesPath = PathResolver.resolveResPath();
final String assetsPath = PathResolver.resolveAssetsPath();
AndroidManifest manifest = new AndroidManifest(
Fs.fileFromPath(manifestPath),
Fs.fileFromPath(resourcesPath),
Fs.fileFromPath(assetsPath)) {
#Override
public int getTargetSdkVersion() {
return TARGET_SDK_VERSION;
}
#Override
public int getMinSdkVersion() {
return MIN_SDK_VERSION;
}
};
return manifest;
}
}
Below, is the link to the example that worked for me. It was developed, however, some time ago and from the time perspective I see it can be done more elegant way so if you decide to apply this solution to your project, organize your path constants to be static and immutable:
https://github.com/dawidgdanski/android-compass-api/blob/master/app-tests/src/test/java/pl/dawidgdanski/compass/PathResolver.java
It's worth remembering that File.separator returns system's default directories separator. It's extremely useful when it comes to provide system-independent paths separated with default separation symbol.
Eventually, if the solution described above is not the one you want to follow, read decent article about setting up testing environment available here:
http://artemzin.com/blog/how-to-mock-dependencies-in-unit-integration-and-functional-tests-dagger-robolectric-instrumentation/
Hope that solves your problem.
In my case, I was running a single test manually (Right click and run) from inside Android Studio and Roboelectric wanted a RELEASE version. The question above was about debug but my test runs for some reason wanted a release version of the manifiest.
java.lang.RuntimeException: build/intermediates/manifests/release/AndroidManifest.xml not found or not a file; it should point to your project's AndroidManifest.xml
I had never done a production build in this project so that build directory had never been created.
After wrestling for a bit with no success (setting the path in configuration, trying to get the path in my CustomRoboelectric file), I just generated a production build so that I had the release path created with a manifest and everything worked.
So my solution was to just run the build to create what Roboelectric wanted.

Robolectric and Android Studio 1.1.0 and library testing

I am having trouble getting my Robolectric unit tests to run under the experimental AS 1.1 unit testing variant. The error I am getting is shown below;
java.lang.NoClassDefFoundError: com/my/app/R$string
at com.my.app.MoneyFormatter.formatDealMessage(MoneyFormatter.java:63)
...
Caused by: java.lang.ClassNotFoundException: com.my.app.R$string
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
at org.robolectric.bytecode.AsmInstrumentingClassLoader.loadClass(AsmInstrumentingClassLoader.java:100)
... 46 more
I retrieve this error by executing from the command line at the root (app-name in Snippet1) the command;
./gradlew core:library:test
This approach works in Android Studio 1.1 without unit testing enabled and using the android gradle tooling at v1.0 (com.android.tools.build:gradle:1.0.0).
Project configuration
My project has a complex structure as prescribed below;
app-name
|
|->app
|->src
|-> androidTest // Espresso helper classes reside here
|-> androidTestFlavour1 // Espresso int tests relating to 'flavour 1'
|-> androidTestFlavour2 // Espresso int tests relating to 'flavour 2'
|-> flavour1
|-> flavour2
|-> main
+ AndroidManifest.xml // Manifest for apps
|-> testFlavour1 // Robolectric unit tests for flavour 1
|-> testFlavour2 // Robolectric unit tests for flavour 2
|->core
|->library
|-> src
|-> main // Library code resides here
+ AndroidManifest.xml
|-> test // Robolectric unit tests for library
+gradle
+gradlew
+settings.gradle
+local.properties
Snippet 1: Project schematic
I have followed various pieces of advice from Pivotal Labs themselves and numerous other home-brew alternatives with no success.
What is the error saying and what do I need to change to put it right?
Update:
So having inspected the classpath being used to run the tests I noted the following library paths;
/workspace/app/core/lib/build/intermediates/bundles/debug/classes.jar
/workspace/app/core/lib/build/intermediates/classes/test/debug
/workspace/app/core/lib/build/intermediates/dependency-cache/test/debug
Neither of these directories contain the generated R$string.class file hence the exception. The directory containing the generated 'R' files include the debug and release build types of the application. So;
/workspace/app/core/lib/build/intermediates/classes/debug
/workspace/app/core/lib/build/intermediates/classes/release
This indicates that a part of the build process is missing adding the "debug" or "release" build type resources. This also backs-up the behaviour whereby pure-Java test cases (that only rely on the classes.jar) work fine.
OK! I've got it working. I have added the following line to the library build gradle to forcibly add the generated "R" files onto the test classpath;
sourceSets {
test.java.srcDirs += "build/generated/source/r/debug"
}
With this I have reached the nirvana of Robolectric tests with;
./gradlew core:lib:test
and Espresso 2.0-based integration with;
./gradlew connectedAndroidTest
Stop the clock for unit and integration test support in the IDE.... oh, what's that? 7 YEARS?! No love lost. Kudos to this answer that showed me how to forcibly add stuff to the test classpath.

Robolectric shadow context not picking up product flavor package name

I'm using Gradle to specify some product flavors in my build, but the package name being found by the shadow application's context isn't the custom one, it's the one in the default manifest. The built apk, though, reports the custom package name.
From the test:
final Context context = Robolectric.getShadowApplication().getApplicationContext();
System.out.println("Package name is: " + context.getPackageName());
According to http://tools.android.com/tech-docs:
Overriding the package name of a flavor has no impact on where the R
class is generated. This is applied when the final APK is created
only.
...which sounds like the flavor is only applied for part of the build process. Any ideas for how to get it to use the flavor when building the tests too?

Categories

Resources