Gradle - add dependency to tests of another module - android

I have a multi-module gradle project that looks like this:
Parent
|--server
|--application (android module)
+--common
The server tests have a dependency on the common module tests. For this, I added
testCompile files(project(':common').sourceSets.test.output.classesDi
compileTestJava.dependsOn tasks.getByPath(':common:testClasses')
and it worked great. Unfortunately, when I tried to do the same thing for the application module that also has a dependency on the common module tests, it wouldn't work. It fails with:
Build file 'application\build.gradle' line: 103
A problem occurred evaluating project ':application'.
Could not find property 'sourceSets' on project ':common'
After googling a bit I also tried
project.evaluationDependsOn(':common')
testCompile files(project(':common').sourceSets.test.output.classesDir)
But fails with another exception:
Project application: Only Jar-type local dependencies are supported. Cannot handle: common\build\classes\test
Any ideas on how to fix this?

There's a couple of approaches solving the problem of importing test classes in this article. https://softnoise.wordpress.com/2014/09/07/gradle-sub-project-test-dependencies-in-multi-project-builds/ The one I used is:
code in shared module:
task jarTest (type: Jar) {
from sourceSets.test.output
classifier = 'test'
}
configurations {
testOutput
}
artifacts {
testOutput jarTest
}
code in module depending on the shared module:
dependencies{
testCompile project(path: ':common', configuration: 'testOutput')
}
And there seems to be a plugin for it as well! https://plugins.gradle.org/plugin/com.github.hauner.jarTest/1.0

Following the approach from sakis, this should be the configuration you need to get the tests available from another project in the Android platform (done for debug variant).
Shared module:
task jarTests(type: Jar, dependsOn: "assembleDebugUnitTest") {
classifier = 'tests'
from "$buildDir/intermediates/classes/test/debug"
}
configurations {
unitTestArtifact
}
artifacts {
unitTestArtifact jarTests
}
Your module:
dependencies {
testCompile project(path: ":libName", configuration: "unitTestArtifact")
}

The solution mentioned by droidpl for Android + Kotlin looks like this:
task jarTests(type: Jar, dependsOn: "assembleDebugUnitTest") {
getArchiveClassifier().set('tests')
from "$buildDir/tmp/kotlin-classes/debugUnitTest"
}
configurations {
unitTestArtifact
}
artifacts {
unitTestArtifact jarTests
}
Gradle for project that is going to use dependencies:
testImplementation project(path: ':shared', configuration: 'unitTestArtifact')

I know it's kinda an old question but the solution mentioned in the following blog solves the problem very nicely and is not a sort of hack or a temporary workaround:
Shared test sources in Gradle multi-module project
It works something like this:
// in your module's build.gradle file that needs tests from another module
dependencies {
testCompile project(path: ':path.to.project', configuration: 'test')
}
Also you should note that in the very last paragraph he mentioned that you need to enable Create separate module per source set in IntelliJ settings. But it works fine without using that option too. Probably due to changes in the recent IntelliJ versions.
EDIT: IntelliJ recognizes this fine as of 2020.x versions.

I think you could use gradles java test fixtures. This will automatically create a testFixtures source set, in which you can write your test that you want to reuse.
Test fixtures are configured so that:
they can see the main source set classes
test sources can see the test fixtures classes
For example, if you have some class in common module:
public class CommonDto {
private final Long id;
private final String name;
// getters/setters and other methods ...
}
Then in the common module, you could write into src/testFixtures/java following utils:
public class Utils {
private static final CommonDto A = new CommonDto(1, "A");
private static final CommonDto B = new CommonDto(2, "B");
public static CommonDto a() { return A; }
public static CommonDto b() { return B; }
}
Then in you other modules you could add this to reuse Utils class
dependencies {
// other dependencies ...
testImplementation(testFixtures(project(":common")))
}
All of this is better explained in the documentation that I provided initially. There are some nuances that you need to take into account until you create this not to leak test classes into production.

Related

Compiler cannot resolve classes in io.ktor.client.features.logging

I'm trying to add logging for Ktor http requests in Android application. According to docs I have to add gradle dependency
implementation "io.ktor:ktor-client-logging:$ktor_version"
and just use this snippet
val client = HttpClient() {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
}
Problem is that compiler "ignores" package 'io.ktor.client.features.logging' added as a dependency. What's strange is that JsonFeature (added as similar dependency) works just fine.
install(JsonFeature) { // perfectly works
...
}
install(Logging) { // unresolved reference
...
}
I already checked .jar file that gradle added to the project, it contains all expected classes, I can open them and see the source code, but magically just can't use in my app. After hours of research I guess it may be somehow related to gradle metadata or that logging feature is multiplatform and some additional gradle configuration is required, but unfortunately I'm not a gradle expert.
I tried adding enableFeaturePreview("GRADLE_METADATA") to settings.gradle, but no effect. Even tried to add "-jvm" to dependency.
implementation "io.ktor:ktor-client-logging-jvm:$ktor_version"
With this dependency Android Studio finding package successfully, but fails to compile with following error
More than one file was found with OS independent path 'META-INF/ktor-http.kotlin_module'
Can anyone please clarify how to properly configure dependency for Ktor logger?
For the ktor-client-logging you have to have the dependency set for each platform:
commonMain {
dependencies {
implementation "ch.qos.logback:logback-classic:1.2.3"
implementation "io.ktor:ktor-client-logging:$ktor_version"
}
}
androidMain {
dependencies {
implementation "io.ktor:ktor-client-logging-jvm:$ktor_version"
}
}
iosMain {
dependencies {
implementation "io.ktor:ktor-client-logging-native:$ktor_version"
}
}
as for the meta META-INF/ktor-http.kotlin_module add to the app/build.gradle inside the android {} block:
android {
packagingOptions {
exclude 'META-INF/common.kotlin_module'
exclude 'META-INF/*.kotlin_module'
}
}

Kotlin Unit Test Not Finding Module Dependency Interface

I have an app module and a domain module. In my domain module I have an interface called Repository. In my app module I use dagger to inject an implementation for this into my class and this works fine.
When I then go to test it using a kotlin unit test, at runtime I get a NoClassDefFoundError.
I have also tried to include the domain module in my app modules dependencies like so but that also did not work:
testImplementation project(':domain')
Here are my current test dependencies and also how I'm including the module
implementation project(':domain')
testImplementation 'junit:junit:4.12'
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
In my unit test I'm using it like this which could be the issue:
#Mock lateinit var mockRepo : Repository
Thanks to #Mark Keen, I was able to find a reported bug on the Jetbrains site.
This contained a solution from a user called #Calin. Adding the following to the projects's build.gradle file and triggering a gradle sync does the trick.
subprojects { subProject ->
afterEvaluate {
if (subProject.plugins.hasPlugin("kotlin") && subProject.plugins.hasPlugin("java-library")) {
subProject.kotlin.copyClassesToJavaOutput = true
subProject.jar.duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
}
}

How module can use resources of another module using gradle multi-module

I have Project A and project B
Project A unit-testings (under the tests dir) need to use resources files which under Projects B main/resources dir.
gradle.build on Project A:
dependencies {
.. testCompile project(':web')
}
gradle.build on Project B:
task testJar(type: Jar) {
classifier 'resources'
from sourceSets.main.resources
}
still failing.
i am not sure what am I missing?
Thank you,
ray.
When you add a dependency on a project like this:
testCompile project(':B')
you're depending on the default artifact produced by project B, which is usually the default jar. If you want to depend on a custom jar, something like a test jar, or a resource jar, or a fat jar instead, you have to explicitly specify that. You can add custom artifacts to configurations, and depend on the configuration instead, as shown below:
in B's build.gradle:
configurations {
foo
}
task testJar(type: Jar) {
classifier 'resources'
from sourceSets.main.resources
}
artifacts {
foo testJar
}
and then use it in A as:
dependencies{
testCompile project(path: ':B', configuration: 'foo')
}
To verify, you can add this task to A:
task printClasspath()<<{
configurations.testCompile.each{println it}
}
which prints:
${projectRoot}\B\build\libs\B-resources.jar

Dagger not generating components for /test class

I am following the guide here: https://github.com/ecgreb/dagger-2-testing-demo
I have the following setup in my app/src/main (the injection and #Provides code omitted):
public class FlingyApplication extends Application {
#Singleton
#Component(modules = { FlingyModule.class })
public interface FlingyComponent
}
#Module
public class FlingyModule
In app/src/test:
public class TestFlingyApplication extends Application {
#Singleton
#Component(modules = { TestFlingyModule.class })
public interface TestFlingyComponent extends FlingyComponent
}
#Module
public class TestFlingyModule
So far, it is nearly identical to the example github. When dagger goes to generate the code for the Component builders in src/main, they generate properly. Dagger does not, however, generate code for the Component builders in src/test.
My main build.gradle:
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.5.1'
}
My app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
# There is obviously more in here, but this is the custom part:
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
}
dependencies {
compile 'com.squareup:otto:1.3.8'
compile 'com.android.support:cardview-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.google.dagger:dagger:2.0.1'
apt 'com.google.dagger:dagger-compiler:2.0.1'
compile 'javax.annotation:javax.annotation-api:1.2'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
testCompile 'com.neenbedankt.gradle.plugins:android-apt:1.4'
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.mockito:mockito-core:1.10.19'
}
So when I build, I get the DaggerFlingyApplication_FlingyComponent class, but not the DaggerTestFlingyApplication_TestFlingyComponent
Something interesting I noticed is that if I switch the line:
apt 'com.google.dagger:dagger-compiler:2.0.1'
# TO
compile 'com.google.dagger:dagger-compiler:2.0.1'
I see the following when I run ./gradlew compileDebugUnitTestSources:
:app:compileDebugJavaWithJavac
Note: /app/build/generated/source/apt/debug/com/jy/flingy/DaggerFlingyApplication_FlingyComponent.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
:app:preDebugUnitTestBuild UP-TO-DATE
:app:prepareDebugUnitTestDependencies
:app:compileDebugUnitTestJavaWithJavac
Note: /app/build/intermediates/classes/test/debug/com/jy/flingy/DaggerTestFlingyApplication_TestFlingyComponent.java uses unchecked or unsafe operations.
I don't know why it builds to intermediates and I assume that I need the build.gradle file to use apt instead of compile, but I can't seem to figure out how to get this to work. I know that it's absolutely possible.
You need to add following to your build.gradle file for instrumentation test:
androidTestApt 'com.google.dagger:dagger-compiler:<version>'
or for JUnit test:
testApt 'com.google.dagger:dagger-compiler:<version>'
This is required to generate Dagger code for your test components.
EDIT:
If you are using jack tool chain then add following
for android test:
androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:<version>'
for JUnit tests:
testAnnotationProcessor 'com.google.dagger:dagger-compiler:<version>'
EDIT:
In case you are using kotlin-kapt for Kotlin code use following:
kaptAndroidTest 'com.google.dagger:dagger-compiler:<version>'
or for JUnit test:
kaptTest 'com.google.dagger:dagger-compiler:<version>'
Check this link for more info.
For Android Studio 3 and dagger 2.13 the already mentioned annotation processors are needed:
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.13'
But also do not forgot to do this for the instrumented test under androidTest:
androidTestAnnotationProcessor'com.google.dagger:dagger-compiler:2.13'
You might get the impression that this alone does not work, because the DaggerXYZ classes are not generated. After hours I found out that the test source generation is only triggered when the tests are executed. If you start a test or androidTest from Android Studio the source generation should be triggered.
If you need this earlier trigger gradle manually:
gradlew <moduledirectory>:compile<Flavor>DebugAndroidTestSources
gradlew <moduledirectory>:compile<Flavor>DebugTestSources
Replace Debug if you run a test in a different build type.
Note:
If you are using multiDexEnable = true you might get an error:
Test running failed: Instrumentation run failed due to
'java.lang.IncompatibleClassChangeError'
Use a different runner in this case:
android {
defaultConfig {
multiDexEnabled true
testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
Just to add a bit to the above answer, since there have been some recent changes.
From Android Gradle plugin version 2.2 and above you will no longer use testApt.
So from now on you need to put only this in the build.gradle:
testAnnotationProcessor 'com.google.dagger:dagger-compiler:<version>'
But more than that, what I came here for, is the following: if you need gradle to generate the DaggerComponent classes for you you will have to do a bit extra work.
Open our build.gradle file and AFTER the android section write this:
android.applicationVariants.all { variant ->
if (variant.buildType.name == "debug") {
def aptOutputDir = new File(buildDir, "generated/source/apt/${variant.unitTestVariant.dirName}")
variant.unitTestVariant.addJavaSourceFoldersToModel(aptOutputDir)
assembleDebug.finalizedBy('assembleDebugUnitTest')
}
}
This will create the directory build/generated/source/apt/test/ as a Java classes recipient and the last part will trigger the "assembleDebugUnitTest" task that will finally create those Dagger2 components in the folder that was just created.
Note that this script is just being triggered for the "debug" variant and takes advantage of that build variant using the "assembleDebug" task. If for some reason you need it in other variants just tweak that a bit.
Why Dagger2 does not do this automatically is beyond me, but hey, I am no pro.
If you added kaptAndroidTest for dagger dependencies and still not getting test components when rebuild your project, try running assembleAndroidTest.
Adding to the above solution and adding the testKapt and androidTestKapt for dagger, I had the problem that my modules and components had the wrong imports as a result of missing imports
e.g
import android.support.test.espresso.core.deps.dagger.Module
import android.support.test.espresso.core.deps.dagger.Module
instead of
import dagger.Module
import dagger.Provides
Hope this helps
Hi even after adding all gradle dependenices and annotations if it still doesnt work then you need to run assembleAndroidTest gradle script for this.
Simply make an empty test case and run it. It will do the job for you.
Cheers
If you are using kotlin use "kaptAndroidTest" to generate dagger component for android tests in your build.gradle file.
I ran ./gradlew build from the command line and got information about a missing Provides method that Android Studio was not telling me about.

Gradle and nested non-transitive dependencies

Here is a test project: click
I have a test Gradle Android project with three modules: app, library_a, library_b. app depends on library_a, then library_a depends on library_b:
build.gradle (app)
dependencies {
...
compile (project(":library_a")){
transitive = false;
}
}
build.gradle (library_a)
dependencies {
...
compile (project(":library_b")){
transitive = false;
}
}
Note that I set transitive = false because I don't want classes from library_b to be accessed from app
Every module has just one class, code is pretty simple:
app:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
ClassA classA = new ClassA();
classA.doSomething();
}
}
library_a:
public class ClassA
{
public void doSomething(){
Log.i("Test", "Done A!");
ClassB classB = new ClassB();
classB.doSomething();
}
}
library_b:
public class ClassB
{
public void doSomething(){
Log.i("Test", "Done B!");
}
}
Well, here is the problem: I'm building my project with gradlew. Apk is compiling successfully, but when I run it I get NoClassDefFoundError.
I/Test﹕ Done A!
E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.NoClassDefFoundError: ru.pvolan.library_b.ClassB
at ru.pvolan.somelibrary.ClassA.doSomething(ClassA.java:12)
...
If I set transitive = true in both .gradle files, it runs ok, but, as I noted above, I don't want dependency to be transitive, as far as I don't want ClassB can be accessed from MainActivity - only ClassA.
What am I doing wrong?
This is a problem that Gradle has simplified in Gradle v3.4.
If you convert library A to use v3.4 there is a simple fix.
Gradle 3.4 changes the "compile" configuration to a set of configurations "api" and "implementation".
First you should upgrade gradle to 3.4 and use the java-library plugin in lieu of the java plugin.
You should use the "api" configuration on any jar that is explicitly used in the API method calls (return type, input parameters, etc).
For all other jars that you want to "hide" (like Library B) you should use the "implementation" configuration. As Library B is only used within the body of implementation methods there is no need to expose it to any other jars at compile time; however it still needs to be available at runtime so Library A can use it.
To implement this your Library A script should replace
apply plugin: 'java'
dependencies {
...
compile (project(":library_b")){
transitive = false;
}
}
with
apply plugin: 'java-library'
dependencies {
implementation project(":library_b")
}
This change will tell Gradle to include Library B as a runtime dependency of app, so that app cannot compile against it, but Library B still will be available at runtime for Library A to use. If for some reason app ends up needing Library B in the future, it would be forced to explicitly include Library B in it's dependency list to ensure it gets the desired version.
See this description from Gradle itself for more details and examples:
https://blog.gradle.org/incremental-compiler-avoidance
The problem is that library_b is a required dependency. You can't simply exclude it, since you need it to be on the classpath at runtime. You are effectively misrepresenting your actual dependencies in order to enforce a code convention and therefore losing any advantage of leveraging a dependency management system like Gradle. If you want to enforce class or package blacklist I'd suggest using a source analysis tool like PMD. Here's an an example of a rule to blacklist specific classes.
If that is not possible for some reason you can get your above example to "work" by simply adding library_b to the runtime classpath of app.
dependencies {
runtime project(':library_b')
}
Do you use multidex?
When I had a problem like this I used multidex and called class from different module. I could fix it only by turning off multidex and running proguard.
UPD
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 21
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.0'
}
more about multi dex https://developer.android.com/tools/building/multidex.html
and about proguard http://developer.android.com/tools/help/proguard.html

Categories

Resources