bryanstern / dagger-instrumentation-example

http://engineering.circle.com/instrumentation-testing-with-dagger-mockito-and-espresso/
102 stars 12 forks source link

ClassNotFoundException on KitKat #1

Open mdrabic opened 9 years ago

mdrabic commented 9 years ago

When I run connectedAndroidTest on either a emulator or physical device running 4.4, the tests fail due to a ClassNotFoundException. However, when I run connectedAndroidTest against an emulator running Lollipop, the tests work as expected and are successful.

Have you had success running the tests on a KitKat device or emulator?

Other details:

Here is the output from the failing tests.

Tests on Nexus_5_API_19(AVD) - 4.4.2 failed: Instrumentation run failed due to 'java.lang.ClassNotFoundException'

com.android.builder.testing.ConnectedDevice > hasTests[Nexus_5_API_19(AVD) - 4.4.2] FAILED 
No tests found.                           
Tests on XT1060 - 4.4.4 failed: Instrumentation run failed due to 'java.lang.ClassNotFoundException'

com.android.builder.testing.ConnectedDevice > hasTests[XT1060 - 4.4.4] FAILED 
No tests found.                           
:app:connectedAndroidTest FAILED          

FAILURE: Build failed with an exception.
bryanstern commented 9 years ago

This was working on KitKat a few weeks ago, but now there seems to be a run-time issue when initializing the dependency graphs during the instrumentation tests. I have no problems building and running the app on KitKat. I'll try digging into this more, but the issue not obvious to me.

Here is the logcat output when the tests are being run against a 4.4 emulator.

I/MonitoringInstrumentation( 2494): Instrumentation Started!
W/dalvikvm( 2494): Class resolved by unexpected DEX: Ldagger/Factory;(0xb1033190):0xab5b8000 ref [Ljavax/inject/Provider;] Ljavax/inject/Provider;(0xb1033190):0xab612000
W/dalvikvm( 2494): (Ldagger/Factory; had used a different Ljavax/inject/Provider; during pre-verification)
I/dalvikvm( 2494): Failed resolving Ldagger/Factory; interface 2164 'Ljavax/inject/Provider;'
W/dalvikvm( 2494): Link of class 'Ldagger/Factory;' failed
I/dalvikvm( 2494): Failed resolving Lcom/circle/testexample/data/DebugDataModule$$ProvideApiFactory; interface 1921 'Ldagger/Factory;'
W/dalvikvm( 2494): Link of class 'Lcom/circle/testexample/data/DebugDataModule$$ProvideApiFactory;' failed
D/AndroidRuntime( 2494): Shutting down VM
W/dalvikvm( 2494): threadid=1: thread exiting with uncaught exception (group=0xb0d2ab20)
E/MonitoringInstrumentation( 2494): Exception encountered by: Thread[main,5,main]. Dumping thread state to outputs and pining for the fjords.
E/MonitoringInstrumentation( 2494): java.lang.NoClassDefFoundError: com/circle/testexample/data/DebugDataModule$$ProvideApiFactory
E/MonitoringInstrumentation( 2494):     at com.circle.testexample.Dagger_Graph.initialize(Dagger_Graph.java:50)
E/MonitoringInstrumentation( 2494):     at com.circle.testexample.Dagger_Graph.<init>(Dagger_Graph.java:42)
E/MonitoringInstrumentation( 2494):     at com.circle.testexample.Dagger_Graph.<init>(Dagger_Graph.java:24)
E/MonitoringInstrumentation( 2494):     at com.circle.testexample.Dagger_Graph$Builder.build(Dagger_Graph.java:84)
E/MonitoringInstrumentation( 2494):     at com.circle.testexample.Graph$Initializer.init(Graph.java:19)
E/MonitoringInstrumentation( 2494):     at com.circle.testexample.App.onCreate(App.java:15)
E/MonitoringInstrumentation( 2494):     at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1007)
E/MonitoringInstrumentation( 2494):     at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4344)
E/MonitoringInstrumentation( 2494):     at android.app.ActivityThread.access$1500(ActivityThread.java:135)
E/MonitoringInstrumentation( 2494):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256)
E/MonitoringInstrumentation( 2494):     at android.os.Handler.dispatchMessage(Handler.java:102)
E/MonitoringInstrumentation( 2494):     at android.os.Looper.loop(Looper.java:136)
E/MonitoringInstrumentation( 2494):     at android.app.ActivityThread.main(ActivityThread.java:5017)
E/MonitoringInstrumentation( 2494):     at java.lang.reflect.Method.invokeNative(Native Method)
E/MonitoringInstrumentation( 2494):     at java.lang.reflect.Method.invoke(Method.java:515)
E/MonitoringInstrumentation( 2494):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
E/MonitoringInstrumentation( 2494):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
E/MonitoringInstrumentation( 2494):     at dalvik.system.NativeStart.main(Native Method)
E/MonitoringInstrumentation( 2494): Caused by: java.lang.ClassNotFoundException: Didn't find class "com.circle.testexample.data.DebugDataModule$$ProvideApiFactory" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/data/app/com.circle.testexample.test-1.apk", zip file "/data/app/com.circle.testexample-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.circle.testexample.test-1, /data/app-lib/com.circle.testexample-1, /vendor/lib, /system/lib]]
E/MonitoringInstrumentation( 2494):     at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
E/MonitoringInstrumentation( 2494):     at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
E/MonitoringInstrumentation( 2494):     at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
E/MonitoringInstrumentation( 2494):     ... 18 more
mdrabic commented 9 years ago

To get past the NoClassDefFoundError for the generated dagger classes, I had to exclude the javax.inject group from the espresso 2.0 dependency.

    androidTestCompile ('com.android.support.test.espresso:espresso-core:2.0') {
        exclude group: 'javax.inject'
    }

However, I am still stuck with the following NoClassDefFoundError when running the tests.

com.circle.testexample.MainActivityTest > testLoginFailure[SM-P600 - 4.4.2] FAILED 
    java.lang.NoClassDefFoundError: org/mockito/internal/matchers/Equals
    at org.mockito.internal.invocation.ArgumentsProcessor.argumentsToMatchers(ArgumentsProcessor.java:47)

com.circle.testexample.MainActivityTest > testLoginSuccess[SM-P600 - 4.4.2] FAILED 
    java.lang.NoClassDefFoundError: org/mockito/internal/matchers/Equals
    at org.mockito.internal.invocation.ArgumentsProcessor.argumentsToMatchers(ArgumentsProcessor.java:47)
:app:connectedAndroidTest FAILED

FAILURE: Build failed with an exception.

I've been working the exact same setup on and off for the past few days. I'm hoping we can get this figured out.

bryanstern commented 9 years ago

Nice find. I'll take a look tonight, but I'm guessing that second issue is the Hamcrest dependency used in both projects.

mdrabic commented 9 years ago

I think you have the right idea about their being a conflict with the Hamcrest dependency.

01-12 12:06:09.834  30378-30391/com.circle.testexample W/dalvikvm﹕ Class resolved by unexpected DEX: Lorg/mockito/ArgumentMatcher;(0x421d8b58):0x78cb0000 ref [Lorg/hamcrest/BaseMatcher;] Lorg/hamcrest/BaseMatcher;(0x421d8b58):0x78855000
01-12 12:06:09.834  30378-30391/com.circle.testexample W/dalvikvm﹕ (Lorg/mockito/ArgumentMatcher; had used a different Lorg/hamcrest/BaseMatcher; during pre-verification)
01-12 12:06:09.834  30378-30391/com.circle.testexample W/dalvikvm﹕ Unable to resolve superclass of Lorg/mockito/ArgumentMatcher; (2185)
01-12 12:06:09.834  30378-30391/com.circle.testexample W/dalvikvm﹕ Link of class 'Lorg/mockito/ArgumentMatcher;' failed
01-12 12:06:09.834  30378-30391/com.circle.testexample W/dalvikvm﹕ Unable to resolve superclass of Lorg/mockito/internal/matchers/Equals; (2224)
01-12 12:06:09.839  30378-30391/com.circle.testexample W/dalvikvm﹕ Link of class 'Lorg/mockito/internal/matchers/Equals;' failed
01-12 12:06:09.839  30378-30391/com.circle.testexample I/TestRunner﹕ failed: testLoginSuccess(com.circle.testexample.MainActivityTest)
bryanstern commented 9 years ago

I found a fix, but I'm not crazy about it. Including Espresso as a debug dependency instead of as a android test dependency solves the issue. I'm not sure why this works.

    debugCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
    debugCompile 'com.google.dexmaker:dexmaker:1.0'
    debugCompile 'org.mockito:mockito-core:1.10.17'

    debugCompile ('com.android.support.test.espresso:espresso-core:2.0') {
        exclude group: 'javax.inject'
    }
    debugCompile 'com.android.support.test:testing-support-lib:0.1'
mdrabic commented 9 years ago

I ran across that as well and didn't really like it. Just to document another bad solution:

    androidTestProvided 'com.android.support.test:testing-support-lib:0.1'
    androidTestProvided ('com.android.support.test.espresso:espresso-core:2.0') {
        exclude group: 'javax.inject'
    }

    // adds the @Generated annoation that Android lacks
    debugCompile ('com.google.dexmaker:dexmaker-mockito:1.0') {
        exclude group: 'org.hamcrest'
    }
    debugCompile ('com.google.dexmaker:dexmaker:1.0') {
        exclude group: 'org.hamcrest'
    }
    debugCompile ('org.mockito:mockito-core:1.10.17') {
        exclude group: 'org.hamcrest'
    }

Using the above configuration will result in the tests passing and the debug build running on the device just fine. It appears when Hamcrest is pulled in by testing-support-lib in the andoridTest build, the Hamcrest dependency in the debug build will be satisfied too.

The bad part is if you try to access anything within Hamcrest when you run just the debug build on the device it will crash with a NoClassDefFoundError. For example, if you add when(api.login(anyString(), anyString())).thenReturn(Observable.just(false)); before the Mocked Api in DebugDataModule is returned the app will crash.

I'm beginning to wonder if this is just a limitation of the build system. I'm not quite sure but we may be running into https://code.google.com/p/android/issues/detail?id=65445.

kboyarshinov commented 9 years ago

Ran into the same problem trying to port u2020-mvp tests to Espresso 2 and JUnit4. Since I am not using Mockito this helped me:

 androidTestCompile ('com.android.support.test.espresso:espresso-core:2.0') {
        exclude group: 'javax.inject'
 }

However, source of the problem is still undefined for me.

emredirican commented 9 years ago

I think the source of the problem above, is about this bug: https://code.google.com/p/android/issues/detail?id=65445

I solved similar exceptions with the following build config ( I am using debugTest for my test specific class, i.e. modules)

debugTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
debugTestCompile 'com.google.dexmaker:dexmaker:1.0'
debugTestCompile 'org.mockito:mockito-core:1.10.17'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
androidTestCompile 'org.mockito:mockito-core:1.10.17'
// Mock web server
debugTestCompile('com.squareup.okhttp:mockwebserver:2.2.0') {
    exclude group: 'com.squareup.okhttp'
}
//Espresso
androidTestCompile ('com.android.support.test.espresso:espresso-core:2.0'){
    exclude group:'javax.inject'
}
androidTestCompile ('com.android.support.test:testing-support-lib:0.1'){
}

However both in my own project and this example project, I am currently getting the following error:

Note: Generating a MembersInjector or Factory for  com.circle.testexample.InjectedBaseActivityTest. Prefer to run the dagger processor over that class instead.
C:\Development\Projects\dagger-instrumentation-example\app\src\debug\java\com\circle\testexample\InjectedBaseActivityTest.java:10: error: Could not generate [com.circle.testexample.InjectedBaseActivityTest$$MembersInjector]: Attempt to recreate a file for type com.circle.testexample.InjectedBaseActivityTest$$MembersInjector.
public class InjectedBaseActivityTest<T extends BaseActivity> extends ActivityInstrumentationTestCase2<T> {
   ^
1 error

I couldn't find any solution to this so far, or any other way to inject the test classes for that matter. Any ideas?

bryanstern commented 9 years ago

@emredirican, I think that error has something to do with the latest changes to Dagger 2. Other projects that I'm using this method in suddenly stopped building today with that same error.

emredirican commented 9 years ago

@bryanstern Indeed, I realized that the error showed up after updating Dagger 2 snapshot. I will take a look again tomorrow. Let me know if you find a solution in the mean time.

GrahamBorland commented 9 years ago

I was able to work around the Generating a MembersInjector or Factory... error by pulling the injected fields out into a base class, and leaving the generics in the subclass.

bryanstern commented 9 years ago

I'm going to clean this up once 1.1 of the Android Gradle Plugin is released. Looks like that version will fix these dependency issues. https://code.google.com/p/android/issues/detail?id=65445