Kotlin Unit Test Not Finding Module Dependency Interface - android

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
}
}
}

Related

how to access to test class from another module to use in junit test?

I split current monolithic module to several modules: :app, :ui, :base.
I wrote test in :ui module using class from :base module.
class FooTest : BaseTest {
#Test
fun fooTest() {}
}
so BaseTest is in :base module but :ui module depends on :base. So AS shows to me that is is ok.
in gradle file for :ui module:
dependences {
implementation project(":base")
}
But when test runs I get:
BaseTest: Unresolved reference: BaseTest
I tried to add:
testImplementation project(":base")
or
androidTestImplementation project(":base")
but not solve the problem. :(
This works if the BaseTest file is in the main directory of :base instead of the test directory
For example, you can have a module called commontest that contains common utils/classes that contain these in the main directory of the module and then to use it in other modules you can include it with testImplementation project(':commontest')

Mockito is not recognized by Android (Kotlin)

Here is my class test:
private const val FAKE_STRING = "APP NAME"
#RunWith(MockitoJUnitRunner::class)
class UnitTestSample {
#Mock
private lateinit var mockContext: Context
#Test
fun readStringFromContext_LocalizedString() {
// Given a mocked Context injected into the object under test...
`when`(mockContext.getString(R.string.app_name))
.thenReturn(FAKE_STRING)
val myObjectUnderTest = ClassUnderTest(mockContext)
// ...when the string is returned from the object under test...
val result: String = myObjectUnderTest.getHelloWorldString()
// ...then the result should be the expected one.
assertThat(result, `is`(FAKE_STRING))
}
}
Here is a piece of my gradle.build.kt (Kotlin DSL):
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
kotlin("android.extensions")
id("com.onesignal.androidsdk.onesignal-gradle-plugin")
jacoco
maven
}
dependencies {
...
//Test base
testImplementation("junit:junit:4.12")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.0.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.0.3")
androidTestImplementation("androidx.test:runner:1.2.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0")
//Unit Tests
testImplementation("org.mockito:mockito-core:3.0.0")
testImplementation("org.mockito:mockito-inline:3.0.0") //support for kotlin final classes
//Android UI Test
androidTestImplementation("org.robolectric:robolectric:3.7.1")
}
As you can see, Android Studio doesn't reognize Mockito. I've already imported org.mockito.junit.MockitoJUnitRunner
I'm running this sample unit test under
src/test/java/.../UnitTestSample.kt
Do you have any idea on how to make it work?
Edit (Solution):
I finally made it work with some help of the comments section. The problem was caused by "maven" plugin import on plugins section, and I didn't see that because the base project I downloaded to convert my Gradle to DSL Kotlin had those plugins working. Somehow this was causing Mockito not to be available at compile time, as #MartinZeitler stated. According to #second, "Maven's runtime does not translate to gradle's runtimeOnly but instead compile".
The error message is pretty clear: an annotation argument must be a compile time argument.
Replace testImplementation with:
debugImplementation "org.mockito:mockito-android:3.2.4"
debugImplementation "org.mockito:mockito-inline:3.2.4"
Edit: Cleaned up the answer
For JUnit5 and mockito use the following dependencies (or newer) and scopes:
testImplementation("org.junit.jupiter:junit-jupiter-api:5.4.2")
compile("org.junit.jupiter:junit-jupiter-engine:5.4.2")
testImplementation("org.mockito:mockito-core:3.0.0")
testImplementation("org.mockito:mockito-junit-jupiter:3.0.0")
testImplementation("org.mockito:mockito-inline:3.0.0")
In your test use the Extension instead of the Runner (which is for JUnit 4).
#ExtendWith(MockitoExtension::class)
When running with the JUnit 5 dependencies of 5.0.3, I got the following error, so consider upgrading to a newer version (as shown in the dependencies above).
java.lang.NoSuchMethodError:
org.junit.platform.commons.support.AnnotationSupport.findAnnotation(Ljava/util/Optional;Ljava/lang/Class;)Ljava/util/Optional;
...
Suppressed: java.lang.NullPointerException
at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:214)
For the maven to gradle conversion I used this site
https://sagioto.github.io/maven2gradle/

How to fix 'java.lang.NoClassDefFoundError' in Android .aar project

I have an android .aar library built and I am trying to integrate it with one of the projects. When the app tries to open the initial screen of the .aar library where I have API call using retrofit. I am getting the below exception
java.lang.NoClassDefFoundError: Failed resolution
of:Lokhttp3/OkHttpClient$Builder;
I have not obfuscated or enabled pro-guard in my .aar project.
Below are my .aar Gradle dependencies
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:converter-gson:2.2.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
OK, this is a common issue. There are several ways to use an android library(aar) in other projects. For example:
By importing this aar as a module into your sample project by using
implementation project(':mylibrary').
By uploading your aar to a maven repository(artifactory, maven local, Jitpack, etc)
Pay attention to this:
If you are using number 1 above, so you will also have to
add(retrofit, okhttp3, etc) to your sample project with the same
version, because the aar by default doesn't include child
dependencies. That's why you are getting that exception
"java.lang.NoClassDefFoundError: Failed resolution of:
Lokhttp3/OkHttpClient$Builder'".
If you are using number 2 above, so you will have to make sure that your pom.xml file includes your child dependencies, because the server needs to download and have them available in your sample project.
What do I recommend?
I recommend developers to use MavenLocal(), it replicates a real scenario before publishing your aar to a public repository like Jitpack or whatever you want.
How can I do it?
Inside build.gradle of your library module:
apply plugin: 'maven-publish'
project.afterEvaluate {
publishing {
publications {
library(MavenPublication) {
setGroupId 'YOUR_GROUP_ID'
//You can either define these here or get them from project conf elsewhere
setArtifactId 'YOUR_ARTIFACT_ID'
version android.defaultConfig.versionName
artifact bundleReleaseAar //aar artifact you want to publish
pom.withXml {
def dependenciesNode = asNode().appendNode('dependencies')
configurations.implementation.allDependencies.each {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
}
}
}
}
}
}
Run assemble and publishToMavenLocal gradle tasks. And you'll see something like this:
In your Sample Project
allprojects {
repositories {
mavenLocal()
...
}
}
implementation '${YOUR_GROUP_ID}:${YOUR_ARTIFACT_ID}:${YOUR_VERSION}'
Let's assume you've built your .aar, and published it to a maven repository (artifactory, nexus, what have you) - e.g., "implementation 'com.mycompany:library:1.0#aar'". Any child dependencies need to be included in the pom.xml delivered by the server to have them available in your application.
Since you've used the "implementation" keyword, those dependencies are considered private to the .aar, and will not be listed in the pom.xml. So they will not be available in your aar, and not automatically imported into the project by gradle.
If you change to the api keyword to "api" instead of implementation, those dependencies become public, and should be listed in the generated pom.xml, and thus should be automatically imported into the project.
This is actually also true also with aar's inside modules, rather than referenced via external systems (e.g. implementation project(':mylibrary') ). If you need the dependencies of mylibrary to run the project, they need to be api.
For reference, you may want to take a look at the Android Studio Dependency Configurations Documentation.
If, however, you're manually including the arr via a files statement (e.g., implementation files('libs/my.aar')), then you don't get automatic dependency management, and you're going to have to add the libraries needed by your aar to the main project manually as well via copy and paste between the build.gradle files.
You can try using fat-aar to solve this https://github.com/kezong/fat-aar-android

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 - add dependency to tests of another module

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.

Categories

Resources