I am attempting to unit test a method that is part of my use case layer of an Android app. The method receives an XML RSS feed and returns it to the view model as GSON-parsed objects. The testing class is annotated with @RunWith(RobolectricTestRunner::class)
.
The test fails because a java.lang.ExceptionInInitializerError
(among others) is thrown by .fromHtml()
within this method of the use case class:
private fun convertHtml(rawString: String): String {
return HtmlCompat
.fromHtml(rawString, HtmlCompat.FROM_HTML_MODE_COMPACT)
.toString()
.replace("", "")
.trim()
}
In reading the stack trace, it appears that this might be an issue with Robolectric trying to initialize a static method or class? I say this because of some of the lines in the stack trace, such as:
at org.robolectric.internal.bytecode.RobolectricInternals.performStaticInitialization(RobolectricInternals.java:61)
at org.robolectric.internal.bytecode.ShadowWrangler.classInitializing(ShadowWrangler.java:178)
Unfortunately, all of my research has gotten me nowhere. I cannot seem to find anything related to this issue. I have seen several threads talk about using Robolectric and PowerMock together for testing static classes, although I am not sure if this is a relevant approach, and I would like to avoid adding more testing frameworks if possible. Why is this exception being thrown, and how does one address this exception?
For reference, here is the whole stack trace:
java.lang.ExceptionInInitializerError
at android.text.SpannableStringBuilder.__constructor__(SpannableStringBuilder.java:61)
at android.text.SpannableStringBuilder.<init>(SpannableStringBuilder.java)
at android.text.HtmlToSpannedConverter.__constructor__(Html.java:425)
at android.text.HtmlToSpannedConverter.<init>(Html.java)
at android.text.Html.fromHtml(Html.java:135)
at android.text.Html.fromHtml(Html.java:101)
at androidx.core.text.HtmlCompat.fromHtml(HtmlCompat.java:150)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.convertHtml(GetNewsHeadlinesUseCase.kt:162)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.convertXmlToJson(GetNewsHeadlinesUseCase.kt:80)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.execute(GetNewsHeadlinesUseCase.kt:35)
at com.domg.testrss.GetNewsHeadlinesUseCaseTest$parsing success$1.invokeSuspend(GetNewsHeadlinesUseCaseTest.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.domg.testrss.GetNewsHeadlinesUseCaseTest.parsing success(GetNewsHeadlinesUseCaseTest.kt:32)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
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.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:570)
at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:278)
at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at org.robolectric.internal.bytecode.ShadowWrangler.classInitializing(ShadowWrangler.java:181)
at org.robolectric.internal.bytecode.RobolectricInternals.classInitializing(RobolectricInternals.java:21)
at android.text.TextUtils.<clinit>(TextUtils.java)
at android.text.SpannableStringBuilder.$$robo$$android_text_SpannableStringBuilder$__constructor__(SpannableStringBuilder.java:61)
at android.text.SpannableStringBuilder.<init>(SpannableStringBuilder.java)
at android.text.SpannableStringBuilder.<init>(SpannableStringBuilder.java)
at android.text.SpannableStringBuilder.<init>(SpannableStringBuilder.java)
at android.text.HtmlToSpannedConverter.$$robo$$android_text_HtmlToSpannedConverter$__constructor__(Html.java:425)
at android.text.HtmlToSpannedConverter.<init>(Html.java)
at android.text.Html.$$robo$$android_text_Html$fromHtml(Html.java:135)
at android.text.Html.fromHtml(Html.java)
at android.text.Html.$$robo$$android_text_Html$fromHtml(Html.java:101)
at android.text.Html.fromHtml(Html.java)
at androidx.core.text.HtmlCompat.fromHtml(HtmlCompat.java:150)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.convertHtml(GetNewsHeadlinesUseCase.kt:162)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.convertXmlToJson(GetNewsHeadlinesUseCase.kt:80)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.execute(GetNewsHeadlinesUseCase.kt:35)
at com.domg.testrss.GetNewsHeadlinesUseCaseTest$parsing success$1.invokeSuspend(GetNewsHeadlinesUseCaseTest.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.domg.testrss.GetNewsHeadlinesUseCaseTest.parsing success(GetNewsHeadlinesUseCaseTest.kt:32)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
... 13 more
Caused by: java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.robolectric.internal.bytecode.RobolectricInternals.performStaticInitialization(RobolectricInternals.java:61)
at org.robolectric.internal.bytecode.ShadowWrangler.classInitializing(ShadowWrangler.java:178)
... 43 more
Caused by: java.lang.NullPointerException
at org.robolectric.shadows.ShadowLegacyAssetManager.getAndResolve(ShadowLegacyAssetManager.java:1019)
at org.robolectric.shadows.ShadowLegacyAssetManager.getResourceText(ShadowLegacyAssetManager.java:285)
at android.content.res.AssetManager.getResourceText(AssetManager.java)
at android.content.res.Resources.getText(Resources.java:225)
at android.content.res.Resources.getString(Resources.java:313)
at android.text.TextUtils.__staticInitializer__(TextUtils.java:1704)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.robolectric.internal.bytecode.RobolectricInternals.performStaticInitialization(RobolectricInternals.java:61)
at org.robolectric.internal.bytecode.ShadowWrangler.classInitializing(ShadowWrangler.java:178)
at org.robolectric.internal.bytecode.RobolectricInternals.classInitializing(RobolectricInternals.java:21)
at android.text.TextUtils.<clinit>(TextUtils.java)
at android.text.SpannableStringBuilder.__constructor__(SpannableStringBuilder.java:61)
at android.text.SpannableStringBuilder.<init>(SpannableStringBuilder.java)
at android.text.HtmlToSpannedConverter.__constructor__(Html.java:425)
at android.text.HtmlToSpannedConverter.<init>(Html.java)
at android.text.Html.fromHtml(Html.java:135)
at android.text.Html.fromHtml(Html.java:101)
at androidx.core.text.HtmlCompat.fromHtml(HtmlCompat.java:150)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.convertHtml(GetNewsHeadlinesUseCase.kt:162)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.convertXmlToJson(GetNewsHeadlinesUseCase.kt:80)
at com.domg.testrss.domain.usecase.GetNewsHeadlinesUseCase.execute(GetNewsHeadlinesUseCase.kt:35)
at com.domg.testrss.GetNewsHeadlinesUseCaseTest$parsing success$1.invokeSuspend(GetNewsHeadlinesUseCaseTest.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.domg.testrss.GetNewsHeadlinesUseCaseTest.parsing success(GetNewsHeadlinesUseCaseTest.kt:32)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
... 13 more
CodePudding user response:
I have discovered a solution. Add the following to the android
section of the module build.gradle
:
testOptions {
unitTests {
includeAndroidResources = true
}
}
According to documentation, setting this to true will allow a unit test "to use Android resources, assets, and manifests" by "perform[ing] resource, asset, and manifest merging before running your unit tests."
Once I added this as described above, the exceptions stopped and my unit tests invoking HtmlCompat
were able to complete.