Debugging Android Unit Tests from the Gradle Command Line
Normally, when you need to debug an Android unit test you would do it from Android Studio. You can set breakpoints in there easily and then run the test in debug mode.
But there may be occasions where you need to debug the test from the Gradle command line. Why? Well, it is rare, but there may be times when a test executes perfectly well in Android Studio, but not when run from the Gradle command line. For example, maybe Gradle executes the unit tests in a particular order, and some tests do not clean up after themselves (like failing to unmock a static object). To find out what is going on in those cases, you may need to debug the tests from how it is run from the Gradle command line.
Doing so is easy. First, set your breakpoints in Android Studio like you normally would. Then run the following Gradle command:
./gradlew test<BuildVariant> --debug-jvm
When you see the following message:
Listening for transport dt_socket at address: 5005
…go into Android Studio, and select the Run/Attach to Process menu option.
You’ll see a window pop up similar to the following. Select the process that is running on port 5005:
Your test will then resume execution, and will stop at the breakpoints you specified.
You can also debug a particular test with the following command
./gradlew test<BuildVariant> --debug-jvm --tests my.package.MyTest
One issue I did encounter is that if I would run the same test, with--debug-jvm
, for a second, third, etc. time would not actually re-execute the test (and thus not await a debugger attachment). It would only do so on the first run. This probably has to do with Gradle caching, because if I changed any of the test or source-under-test code then it would re-execute the test and await a debugger attachment. It makes sense that Gradle would do this if you are simply executing a test without debugging, because if nothing changed then it is reasonable to assume your test results should be the same (and if not, then it is probably a sign of a poorly-written test). But in this case it is a bit unfortunate because often you may need to debug a test more than once to get to the root cause of a problem (like changing the breakpoints to pinpoint the problem). But you could always get around that by simply doing a trivial change to the test code to get Gradle to execute it again. Or, you can specify the --rerun-tasks
option to get Gradle to recompile and rerun your tests, although that will take much longer to execute as it will re-execute all dependent tasks. But, either approach would work.
If you use this debugging approach, make sure you are running the Gradle command exactly the same way as the Gradle command-line that exhibits the error. For example, if you are seeing your unit tests fail in a Jenkins build, be sure you run the Gradle command the same way (e.g., the same command-line options), with of course the addition of the--debug-jvm
option. One case I seem to remember is that the execution order chosen by Gradle was impacting the tests, for example, if the tests are not hermetic like they should be and they mock global objects.
This also brings up the fact that this approach will not necessarily surface the error you are trying to debug. For example, if you run your regular Gradle command-line and see the unit tests fail, adding the --debug-jvm
will not necessarily surface that error. Rather, doing so can result in an Observer Effect. That can be especially true if the bug you are trying to fix is a result of a race condition because of parallel execution of your tests. If that happens, something simpler like “caveperson debugging” (debugging via print statements) may be a better approach (although even that can cause an Observer Effect). So just bear that in mind.
I want to emphasize that, most of the time, debugging from Android Studio is more than sufficient, and of course way more convenient, for debugging your unit tests. You should definitely favor that approach versus the one in this blog post. But for the rare occasions where you need to get at why your unit tests fail from the Gradle command-line, the above is another option you can try.