Silently throwing robolectric RunTimeException in unit test
OKHttpVersion used : 5.2.0
Reproduction Steps
- Create an empty Android project
- Add OkHttp depdencies
// define a BOM and its version
implementation(platform("com.squareup.okhttp3:okhttp-bom:5.2.0"))
// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")
- In
ExampleUnitTest, add thisOkHttpClient.Builder().build()in the first line ofaddition_isCorrect.
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
OkHttpClient.Builder().build()
assertEquals(4, 2 + 2)
}
}
- Run the test
- The test passes but there's runtime exception being thrown silently that's causing a a lot of noise in the test output
Possibly running android unit test without robolectric
java.lang.RuntimeException: Method isLoggable in android.util.Log not mocked. See https://developer.android.com/r/studio-ui/build/not-mocked for details.
at android.util.Log.isLoggable(Log.java)
at okhttp3.internal.platform.android.AndroidLog.enableLogging(AndroidLog.kt:127)
at okhttp3.internal.platform.android.AndroidLog.enable(AndroidLog.kt:109)
at okhttp3.internal.platform.PlatformRegistry.findPlatform(PlatformRegistry.kt:25)
at okhttp3.internal.platform.Platform$Companion.findPlatform(Platform.kt:225)
at okhttp3.internal.platform.Platform$Companion.access$findPlatform(Platform.kt:203)
at okhttp3.internal.platform.Platform.<clinit>(Platform.kt:204)
at okhttp3.OkHttpClient.<init>(OkHttpClient.kt:300)
at okhttp3.OkHttpClient$Builder.build(OkHttpClient.kt:1384)
at com.example.okhttptest.ExampleUnitTest.addition_isCorrect(ExampleUnitTest.kt:17)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:112)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:40)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:54)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:92)
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:183)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:132)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:121)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
As of version 5.0 OkHttp has an Android variant and will access Android APIs without guards. As such, you need to provide the appropriate test environment, which is either: enabling return default values true, Robolectric, or running unit tests via instrumentation on emulators or real devices.
As of version 5.0 OkHttp has an Android variant and will access Android APIs without guards. As such, you need to provide the appropriate test environment, which is either: enabling return default values true, Robolectric, or running unit tests via instrumentation on emulators or real devices.
I found a related issue that the test start crashing with paparazzi plugin added. To replicate, add id("app.cash.paparazzi") version "2.0.0-alpha02" at the module level gradle , and this silent error become fatal crash.
boolean android.util.Log.isLoggable(java.lang.String, int)'
java.lang.UnsatisfiedLinkError: 'boolean android.util.Log.isLoggable(java.lang.String, int)'
at android.util.Log.isLoggable(Native Method)
at okhttp3.internal.platform.android.AndroidLog.enableLogging(AndroidLog.kt:127)
at okhttp3.internal.platform.android.AndroidLog.enable(AndroidLog.kt:109)
at okhttp3.internal.platform.PlatformRegistry.findPlatform(PlatformRegistry.kt:25)
at okhttp3.internal.platform.Platform$Companion.findPlatform(Platform.kt:225)
at okhttp3.internal.platform.Platform$Companion.access$findPlatform(Platform.kt:203)
at okhttp3.internal.platform.Platform.<clinit>(Platform.kt:204)
at okhttp3.OkHttpClient.<init>(OkHttpClient.kt:300)
I've tired returning default value true in gradle with following code but that didn't fix it.
testOptions {
unitTests.all {
unitTests.isReturnDefaultValues = true
}
}
Should this paparazzi plugin integration also be an issue or an expected behavior? If it'll be considered as issue, I can up a reproducible repository and open an issue in Paparazzi.
A workaround that works for us now is we can explicitly override to use jvm artifact for test by adding testImplementation "com.squareup.okhttp3:okhttp-jvm:5.2.0" since we don't use Robolectric for our unit tests.
Paparazzi brings a JVM version of the compiled Android framework which is trying to link the native code that Log bottoms out in. It sits in the classpath before the mockable jar that AGP creates, which is why that setting no longer has any effect.
The behavior you're seeing is the same you'd see for any Android library that touched Log first before any other Android APIs.
Forcing the JVM variant may work for now, although it's not guaranteed that Android-specific APIs may come in the future which would not be available on the JVM variant causing NoClassDefFoundError or NoSuchMethodException. Aside from the three options above, you could also swap out OkHttp with something else either at a higher-layer of abstraction (like a service interface) or even just choosing to have your entry point take a Call.Factory and making a simple fake.
This seems like something we should make safe https://github.com/square/okhttp/pull/9137
At least the intent was there.