Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LeakCanary in Unit Test Artifact #143

Closed
linggom opened this issue May 22, 2015 · 33 comments
Closed

LeakCanary in Unit Test Artifact #143

linggom opened this issue May 22, 2015 · 33 comments

Comments

@linggom
Copy link

linggom commented May 22, 2015

Hi.

I got NPE when i run test using Unit Test build variants. Do you have any best practice to do this ?

java.lang.NullPointerException
    at com.squareup.leakcanary.internal.LeakCanaryInternals.isInServiceProcess(LeakCanaryInternals.java:130)
    at com.squareup.leakcanary.LeakCanary.isInAnalyzerProcess(LeakCanary.java:146)
    at com.squareup.leakcanary.LeakCanary.install(LeakCanary.java:48)
    at com.squareup.leakcanary.LeakCanary.install(LeakCanary.java:37)
    at com.vidio.android.VidioApplication.onCreate(VidioApplication.java:18)
    at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:131)
    at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:431)
    at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:224)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:168)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:69)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:105)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:360)
    at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

Is it caused by multiple flavour on release ?

@linggom linggom changed the title LeakCanary in Unit Test Build Variants LeakCanary in Unit Test Artifact May 22, 2015
@pyricau
Copy link
Member

pyricau commented May 22, 2015

No. You just need to read the code to figure it out. It's because you're using Robolectric, which doesn't set a processName on the serviceInfo returned by PackageManager.getServiceInfo().

Instead, you can let your application class have a method that can be overridden in unit tests:

public class VideoApplication extends Application {
  public void onCreate() {
    super.onCreate();
    if (!isInUnitTests()) {
      LeakCanary.install(this);
    }
  }

  protected boolean isInUnitTests() {
    return false;
  }
}

Then you just need to create a TestVideoApplication in your test code and the same package, and Robolectric will automatically use that instead:

public class TestVideoApplication extends VideoApplication {
    protected boolean isInUnitTests() {
    return true;
  }
}

@pyricau
Copy link
Member

pyricau commented Jul 20, 2015

TODO: Update README to mention Robolectric

@romainpiel
Copy link

You can also disable leakCanary for unit tests:

testCompile "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryVersion}"

@ygnessin
Copy link
Contributor

If your tests run in debug mode like mine, to disable LeakCanary you'll need your testCompile declaration to be above debugCompile:

// WORKS
androidTestCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'

// DOESN'T WORK
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
androidTestCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'

Ran into this issue today so thought I'd share in case anyone else is confused

ylogx added a commit to ylogx/leakcanary that referenced this issue Nov 16, 2015
@ylogx
Copy link
Contributor

ylogx commented Nov 16, 2015

Propose to close based on PR above

@pyricau
Copy link
Member

pyricau commented Jan 4, 2016

@ygnessin @shubhamchaudhary I don't recall: which one works by default on a new Android project, androidTestCompile or testCompile? I'd like the README to mention the most standard one.

@ygnessin
Copy link
Contributor

ygnessin commented Jan 5, 2016

@pyricau I think testCompile is for unit tests, which is probably the closest thing to "default" there is. I use androidTestCompile for the test API that I wrote which runs on a device; probably less common. Sorry for the confusion!

@pyricau
Copy link
Member

pyricau commented Jan 5, 2016

thx!

@jaredsburrows
Copy link
Contributor

@ygnessin This works for me in all of my projects:

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // We only want leakcanary in debug builds

dogfoodCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // no-op

releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // no-op

testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // no-op

@pyricau
Copy link
Member

pyricau commented Sep 18, 2016

The readme now has this:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4'
 }

So we can close this issue.

@pyricau pyricau closed this as completed Sep 18, 2016
raatiniemi added a commit to raatiniemi/worker that referenced this issue Sep 23, 2016
installing leakcanary causes the robolectric unit tests to throw a
`NullPointerException` when running from within android studio
(running from the command-line seem fine).

as a, hopefully temporary, work-around the leakcanary installation
is excluded when running the unit tests.

ref:
square/leakcanary#143 (comment)
@caarmen
Copy link

caarmen commented Oct 26, 2017

I'm seeing this NPE now in unit tests, that I've updated my environment (android gradle plugin 3.0.0)

I have this in my gradle file:

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    androidTestImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

I tried changing the order of these statements, but so far haven't had luck getting rid of the NPE in unit tests, by trying to use the no-op version of the lib for tests.

Note: the same issue occurs if i keep the old testCompile syntax instead of the new testImplementation.

Note: tests run fine from within Android Studio. The error occurs running tests on the command line with ./gradlew clean testDebugUnitTest

Update: ok, if I RTFM and create a test application class, and override the setupLeakCanary() as described, the tests now work on the command line. :)

However, it would be nice to just not use leak canary in tests by using the no-op version of the lib.

@Kisty
Copy link

Kisty commented Jan 29, 2018

As per @JakeWharton's #907 PR, it suggests to do the following without subclassing Application (win!):

Taken from README.md

To disable LeakCanary in unit tests, add the following to your build.gradle:

// Ensure the no-op dependency is always used in JVM tests.
configurations.all { config ->
  if (config.name.contains('UnitTest')) {
    config.resolutionStrategy.eachDependency { details ->
      if (details.requested.group == 'com.squareup.leakcanary' && details.requested.name == 'leakcanary-android') {
        details.useTarget(group: details.requested.group, name: 'leakcanary-android-no-op', version: details.requested.version)
      }
    }
  }
}

@AndreSand
Copy link

@Kisty Thanks for the answer! it works :)

@Gloix
Copy link

Gloix commented May 10, 2018

Adding @JakeWharton's suggestion still causes NPEs in my setup. I added the config lines after the dependencies block in my app module's build.gradle, as well as adding the no-op version directly using testImplementation.
Using Gradle 4.6, Leak Canary 1.5.4, Android Studio 3.1.2 on High Sierra and Robolectric 3.8.

java.lang.NullPointerException
	at com.squareup.leakcanary.internal.LeakCanaryInternals.isInServiceProcess(LeakCanaryInternals.java:112)
	at com.squareup.leakcanary.LeakCanary.isInAnalyzerProcess(LeakCanary.java:145)
	...

@Kisty
Copy link

Kisty commented May 10, 2018

@Gloix can you post your dependency configuration with just the leak canary dependencies + the snippet Jake posted? Perhaps somethings in the wrong order.

@Gloix
Copy link

Gloix commented May 10, 2018

@Kisty Nevermind. After a couple of rebuilds it is working now :D, but thanks anyway.

@Gloix
Copy link

Gloix commented May 11, 2018

@Kisty I don't know what happened yesterday but my setup failed again, but this time I went deeper and pinpointed the set of possible configurations my test is running on:

prodDebugCompile, prodDebugApk, prodDebugProvided, prodDebugApi, prodDebugImplementation, prodDebugRuntimeOnly, prodDebugCompileOnly, prodDebugWearApp, prodDebugAnnotationProcessor, prodDebugCompileClasspath, prodDebugAnnotationProcessorClasspath, prodDebugRuntimeClasspath, prodDebugWearBundling, prodDebugBundleElements, prodDebugRuntimeElements, prodDebugApiElements, prodDebugMetadataElements

none of them contains the substring AndroidTest or UnitTest, but all of them contain the substring prodDebug. It seems my tests are not running with a test-like gradle config and I'm kind of lost as to what I can do to tell Android Studio to run my tests using a test-like gradle config.

@pyricau pyricau modified the milestone: 1.6 Jun 18, 2018
@kschults
Copy link

@Gloix Were you ever able to resolve this? Adding the configurations block fixed it for me, but only for running from the command line. If I run tests from within the IDE, it still doesn't use the no-op artifact, and the tests fail.

@Gloix
Copy link

Gloix commented Jun 21, 2018

@kschults I never tried running it through the command line. I fixed it by killing leakcanary until I need it again.

@kschults
Copy link

@Kisty Here's a shortened version of my build.gradle. This version works fine when running from the command line ./gradlew :app:test<build type + flavor>UnitTest, but not when running from the buttons in the IDE.

buildscript { ... }
configurations.all { config ->
    // Ensure the no-op leak canary dependency is always used in tests.
    if (config.name.contains('UnitTest') || config.name.contains("AndroidTest")) {
        config.resolutionStrategy.eachDependency { details ->
            if (details.requested.group == 'com.squareup.leakcanary' && details.requested.name == 'leakcanary-android') {
                details.useTarget(group: details.requested.group, name: 'leakcanary-android-no-op', version: details.requested.version)
            }
        }
    }
}

...

android { ... }
dependencies {
...
    testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    qaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

Overriding my application in test works, but it would definitely be better if I could just use the no-op version. I've looked at the dependency graph generated by ./gradlew :app:dependences, and it looks like it should be using the no-op version, but when I run it, it still uses the full artifact.

@Kisty
Copy link

Kisty commented Jun 21, 2018

Try swapping the dependency line for test and debug so that the test dependency comes after debug. I wonder if the debug line is overwriting the test dependency. It's worth a try.

@kschults
Copy link

No luck. Same issue

@Kisty
Copy link

Kisty commented Jun 21, 2018

Ok, how about trying the Canary build of Android Studio (download and install separately http://d.android.com/studio/preview). I know the team are aware of unit test issues in Android Studio 3.1 with Robolectric. Do you have Robolectric in unit tests?

@kschults
Copy link

Yeah, it's Robolectric. I'll give that a try (probably tomorrow) and get back to you

@kschults
Copy link

@Kisty Unfortunately, I'm getting the same results on the Canary build (3.2 Beta 1)

@IgorGanapolsky
Copy link

It is still the same issue in Android Studio 3.3 Canary 5

@mikehc
Copy link

mikehc commented Apr 6, 2019

@Kisty Here's a shortened version of my build.gradle. This version works fine when running from the command line ./gradlew :app:test<build type + flavor>UnitTest, but not when running from the buttons in the IDE.

buildscript { ... }
configurations.all { config ->
    // Ensure the no-op leak canary dependency is always used in tests.
    if (config.name.contains('UnitTest') || config.name.contains("AndroidTest")) {
        config.resolutionStrategy.eachDependency { details ->
            if (details.requested.group == 'com.squareup.leakcanary' && details.requested.name == 'leakcanary-android') {
                details.useTarget(group: details.requested.group, name: 'leakcanary-android-no-op', version: details.requested.version)
            }
        }
    }
}

...

android { ... }
dependencies {
...
    testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
    qaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}

Overriding my application in test works, but it would definitely be better if I could just use the no-op version. I've looked at the dependency graph generated by ./gradlew :app:dependences, and it looks like it should be using the no-op version, but when I run it, it still uses the full artifact.

Having the same issues when upgrading from version 1.6.1. When I use 1.6.2 or 1.6.3 I get the NPE, and when debugging seems to be using the full artifact and not the no-op ones. This happens using Robolectric 4.0.1 and 4.2.1 (latests available to date).

@Kisty
Copy link

Kisty commented Apr 8, 2019

OK, perhaps it's favouring the debug artefact over the test one for testDebug. Have you tried:

testDebugImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

@mikehc
Copy link

mikehc commented Apr 8, 2019

@Kisty same result.

I ran the test in console with ./gradlew test and they run just fine but not on Android Studio. If I downgrade to version 1.6.1 they can run on Android Studio too.

@Kisty
Copy link

Kisty commented Apr 9, 2019

@mikehc Sounds like the classpath resolution is different on AS from Gradle. Tried editing the run config so it uses JAR manifest or something else?

Another few things to try from Android Studio:

  1. Build->Clean Project then Build->Make project (try doing two separate actions because I've had files locked and thus doesn't do a complete clean)
  2. File->Invalidate cache and restart

@mikehc
Copy link

mikehc commented Apr 10, 2019

Tried editing the run config so it uses JAR manifest

@Kisty Can you give me some pointers of how to do that?

@Kisty
Copy link

Kisty commented Apr 11, 2019

@mikehc

  1. Click dropdown for run list and select Edit configurations
    run-config-1
  2. Find unit test run task in popup
  3. Ensure that JRE is selected to Java 1.8 JRE not Android API...
  4. Select an option from Shorten command line and try running again.
    run-config-2

@renetik
Copy link

renetik commented Jul 20, 2022

I was wrong probably :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests