tmurakami/dexopener

Using multidex + "dexopener" - java.lang.NoClassDefFoundError

jaredsburrows opened this issue · 14 comments

Reposted from: mockito/mockito#1082 (comment)

Starting 0 tests on emulator-5554 - 4.4.2
Tests on emulator-5554 - 4.4.2 failed: Instrumentation run failed due to 'java.lang.NoClassDefFoundError'

com.android.builder.testing.ConnectedDevice > No tests found.[emulator-5554 - 4.4.2] FAILED 
No tests found. This usually means that your test classes are not in the form that your test runner expects (e.g. don't inherit from TestCase or lack @Test annotations).

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':connectedDebugAndroidTest'.
> There were failing tests. See the report at: file:///Users/<>/repo/android-gif-example/build/reports/androidTests/connected/index.html

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED in 6s
94 actionable tasks: 39 executed, 55 up-to-date

Version of dexmaker - 0.10.1

I was using my example project here: https://github.com/jaredsburrows/android-gif-example

Your project seems to use neither Multidex nor DexOpener.
Could you tell me the steps to reproduce?

I just applied multidex and your library along with mockito-android library.

Are you using Multidex for your test apk?
If so, AndroidJUnitRunner does not support mulitdex for test apks, so DexOpener does not support it.

https://developer.android.com/studio/build/multidex.html?hl=en#testing

Notes:

  • Don't use MultiDexTestRunner, which is deprecated; use AndroidJUnitRunner instead.
  • Using multidex to create a test APK is not currently supported.

@tmurakami

Yes, I use the AndroidJUnitRunner.

Multidex 1.0.2 is out:
https://developer.android.com/topic/libraries/support-library/revisions.html#25-4-0

Concurrent with this support library release, we are also releasing multidex version 1.0.2. This version includes the following important changes:
Allows multidexing of instrumentation APK.
Deprecates MultiDexTestRunner (AndroidJUnitRunner should be used instead).
Provides better protection against some bad archive extraction management of the app.
Fixes a bug that could lead to abandoned temporary files.
Provides faster installation when done in concurrent process.
Fixes an installation bug on API 19 and 20.
https://developer.android.com/topic/libraries/support-library/revisions.html#25-4-0

In theory multidex for the test APK should work. But the fact of the matter is using your library + mockito-android makes it multidex anyways.

Thank you for your information.

First I tried Multidex 1.0.2 without using DexOpener, but your instrumented tests does not seem to work.

$ ./gradlew clean connectedDebugAndroidTest

[snip]

Starting 0 tests on Nexus_5_API_19(AVD) - 4.4.2
Tests on Nexus_5_API_19(AVD) - 4.4.2 failed: Instrumentation run failed due to 'java.lang.NoClassDefFoundError'

com.android.builder.testing.ConnectedDevice > No tests found.[Nexus_5_API_19(AVD) - 4.4.2] FAILED 
No tests found. This usually means that your test classes are not in the form that your test runner expects (e.g. don't inherit from TestCase or lack @Test annotations).

FAILURE: Build failed with an exception.

Logcat is here:

E/AndroidRuntime( 3574): FATAL EXCEPTION: Instr: android.support.test.runner.AndroidJUnitRunner
E/AndroidRuntime( 3574): Process: burrows.apps.example.gif.debug, PID: 3574
E/AndroidRuntime( 3574): java.lang.NoClassDefFoundError: org.junit.runner.manipulation.Filter$1
E/AndroidRuntime( 3574): 	at org.junit.runner.manipulation.Filter.<clinit>(Filter.java:21)
E/AndroidRuntime( 3574): 	at android.support.test.internal.runner.TestRequestBuilder.<init>(TestRequestBuilder.java:83)
E/AndroidRuntime( 3574): 	at android.support.test.internal.runner.TestRequestBuilder.<init>(TestRequestBuilder.java:525)
E/AndroidRuntime( 3574): 	at android.support.test.runner.AndroidJUnitRunner.createTestRequestBuilder(AndroidJUnitRunner.java:370)
E/AndroidRuntime( 3574): 	at android.support.test.runner.AndroidJUnitRunner.buildRequest(AndroidJUnitRunner.java:343)
E/AndroidRuntime( 3574): 	at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:260)
E/AndroidRuntime( 3574): 	at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701)
W/ActivityManager( 1568): Error in app burrows.apps.example.gif.debug running instrumentation ComponentInfo{burrows.apps.example.gif.test/android.support.test.runner.AndroidJUnitRunner}:
W/ActivityManager( 1568):   java.lang.NoClassDefFoundError
W/ActivityManager( 1568):   java.lang.NoClassDefFoundError: org.junit.runner.manipulation.Filter$1
I/ActivityManager( 1568): Force stopping burrows.apps.example.gif.debug appid=10067 user=0: finished inst
I/ActivityManager( 1568): Killing 3574:burrows.apps.example.gif.debug/u0a67 (adj 0): stop burrows.apps.example.gif.debug
I/ActivityManager( 1568): Delay finish: com.google.android.gms/.stats.service.DropBoxEntryAddedReceiver
I/ActivityManager( 1568): Resuming delayed broadcast

Could you please point out any problems with the following changes?

diff --git a/build.gradle b/build.gradle
index c45e8e0..94567bb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -55,6 +55,7 @@ android {
     versionName "1.0"
     minSdkVersion rootProject.ext.lollipop ? 21 : rootProject.ext.minSdkVersion // Optimize build speed - build with minSdk 21 if using multidex
     targetSdkVersion rootProject.ext.targetSdkVersion
+    multiDexEnabled true
     testApplicationId "burrows.apps.example.gif.test"
     testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     resConfigs "en"                                                             // Optimize APK size - keep only english resource files for now
@@ -158,9 +159,11 @@ android {
 configurations.all {
   resolutionStrategy.force deps.supportAnnotations
   resolutionStrategy.force deps.kotlinStdlib
+  resolutionStrategy.force 'com.android.support:multidex:1.0.2'
 }
 
 dependencies {
+  compile 'com.android.support:multidex:1.0.2'
   // Android/Google
   compile deps.design
   compile deps.cardviewv7
@@ -187,8 +190,9 @@ dependencies {
   androidTestCompile deps.junit
   androidTestCompile deps.assertjCore
   androidTestCompile deps.mockitoKotlin, { exclude group: "net.bytebuddy" }     // DexMaker has it"s own MockMaker
-  androidTestCompile deps.mockitoCore, { exclude group: "net.bytebuddy" }       // DexMaker has it"s own MockMaker
-  androidTestCompile deps.dexmakerMockito, { exclude group: "net.bytebuddy" }   // DexMaker has it"s own MockMaker
+//  androidTestCompile deps.mockitoCore, { exclude group: "net.bytebuddy" }       // DexMaker has it"s own MockMaker
+//  androidTestCompile deps.dexmakerMockito, { exclude group: "net.bytebuddy" }   // DexMaker has it"s own MockMaker
+  androidTestCompile 'org.mockito:mockito-android:2.8.47'
   androidTestCompile deps.runner
   androidTestCompile deps.espressoCore
   androidTestCompile deps.espressoIntents
diff --git a/src/main/kotlin/burrows/apps/example/gif/App.kt b/src/main/kotlin/burrows/apps/example/gif/App.kt
index ae5c2ea..baed7f9 100644
--- a/src/main/kotlin/burrows/apps/example/gif/App.kt
+++ b/src/main/kotlin/burrows/apps/example/gif/App.kt
@@ -1,7 +1,7 @@
 package burrows.apps.example.gif
 
 import android.annotation.SuppressLint
-import android.app.Application
+import android.support.multidex.MultiDexApplication
 import burrows.apps.example.gif.presentation.di.component.ActivityComponent
 import burrows.apps.example.gif.presentation.di.component.AppComponent
 
@@ -9,7 +9,7 @@ import burrows.apps.example.gif.presentation.di.component.AppComponent
  * @author [Jared Burrows](mailto:jaredsburrows@gmail.com)
  */
 @SuppressLint("Registered")
-open class App : Application() {
+open class App : MultiDexApplication() {
   lateinit var appComponent: AppComponent
   lateinit var activityComponent: ActivityComponent
 

Perhaps I found a solution.

  1. Create your own JUnitRunner as follows
class JUnitRunner : AndroidJUnitRunner() {
  override fun onCreate(arguments: Bundle?) {
    MultiDex.installInstrumentation(context, targetContext)
    super.onCreate(arguments)
  }
}
  1. Set JUnitRunner as the default test instrumentation runner
android {
    defaultConfig {
        // testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        testInstrumentationRunner "your.own.JUnitRunner"
    }
}

If you use DexOpener, change the base class of your own JUnitRunner to DexOpenerAndroidJUnitRunner.

class JUnitRunner : DexOpenerAndroidJUnitRunner() {
  override fun onCreate(arguments: Bundle?) {
    MultiDex.installInstrumentation(context, targetContext)
    super.onCreate(arguments)
  }
}

But now, due to the issue #8, DexOpener does not work with your project.
As a workaround, add DataBindingUtil::class.java into App#onCreate().

open class App : Application() {
  lateinit var appComponent: AppComponent
  lateinit var activityComponent: ActivityComponent

  override fun onCreate() {
    super.onCreate()

    DataBindingUtil::class.java // Add this line

    // Setup components
    appComponent = AppComponent.init(this)
    activityComponent = ActivityComponent.init(appComponent)
  }
}

We have released version 0.10.2 which fixed the issue #8.

@tmurakami Thank you so much! I will try it in my project soon!

@tmurakami If you do not mind me asking, how did you know about installInstrumentation ? I do not see it in the documentation: https://developer.android.com/reference/android/support/multidex/MultiDex.html.

I had a mistake.
I overlooked that your app uses applicationIdSuffix.
There is a problem with the version 0.10.2, so you should use 0.10.1.

DexOpenerAndroidJUnitRunner does not work well with applicationIdSuffix.
Instead you should use DexOpener#builder(Context) on your JUnitRunner.

class JUnitRunner : AndroidJUnitRunner() {
  override fun newApplication(cl: ClassLoader, className: String?, context: Context): Application {
    DexOpener.builder(context)
      .openIf { it.startsWith("burrows.apps.example.gif") }
      .build()
      .installTo(cl)
    return super.newApplication(cl, className, context)
  }
}

@tmurakami Thanks for sharing the source. I guess the documentation is not up yet.

Thanks, I will look into it.

This issue seems to be solved, so I will close this.
If there is any problem, please reopen this issue.

Thank you.