Short question
How can I sucessfully load an asset image in an androidTest with Picasso or Glide?
Long explaination
In an UI test I want to invoke my application to load images into an ImageView with certain operations applied to them (e.g. cropping, centering, etc.). To perform the image loading, cropping, etc., I use Picasso. In the UI test I want to verify that after triggering the loading via UI the bitmap attached to the ImageView is exactly what I intended using Picasso, i.e. I’m connecting Picasso and applying the Picasso DSL correctly.
To do that I spin up an androidTest, supplying the images to verify with as assets in src/androidTest/resources/assets
. I inject file:///android_asset/$filename
as image filename into my application to have Picasso use the AssetManager
for loading the image.
Although the application works with regular images stored on the device / in the emulator, the test fails loading the asset images -- the AssetManager reports a FileNotFoundException:
Caused by: java.io.FileNotFoundException: australia.jpg
at android.content.res.AssetManager.nativeOpenAsset(Native Method)
at android.content.res.AssetManager.open(AssetManager.java:744)
at android.content.res.AssetManager.open(AssetManager.java:721)
at com.squareup.picasso.AssetRequestHandler.load(AssetRequestHandler.java:45)
at com.squareup.picasso.BitmapHunter.hunt(BitmapHunter.java:206)
at com.squareup.picasso.RequestCreator.get(RequestCreator.java:396)
By the way, using Glide leads to the same FileNotFoundException.
I’ve set up a small instrumented test AssetTest.kt
to analyze and showcase the problem. Please follow the link to view it completely on GitHub.
@RunWith(AndroidJUnit4::class)
@LargeTest
class AssetTest {
private lateinit var activityScenario: ActivityScenario<TasksActivity>
private val filename = "victoria-priessnitz-KBIui3I44SY-unsplash.jpg"
// NOTE: the tests show the same behavior regardless of whether the activity is launched or not
private val enableActivityUsage = true
@Before
fun launchActivity() {
if (enableActivityUsage) {
activityScenario = ActivityScenario.launch(TasksActivity::class.java)
}
}
@After
fun shutdownActivity() {
if (enableActivityUsage) {
activityScenario.close()
}
}
@Test
fun loadingAssetImageWithInstrumentationContextSucceeds() {
val ctx = InstrumentationRegistry.getInstrumentation().context
val inputStream = ctx.resources.assets.open(filename)
val bytes = inputStream.readBytes()
assertEquals(1903567, bytes.size)
}
@Test(expected = FileNotFoundException::class)
fun loadingAssetImageWithInstrumentationTargetContextFails() {
val targetCtx = InstrumentationRegistry.getInstrumentation().targetContext
val inputStream = targetCtx.resources.assets.open(filename)
inputStream.readBytes()
}
@Test(expected = FileNotFoundException::class)
fun loadingAssetImageWithInstrumentationTargetContextsApplicationContextFails() {
val targetCtx = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
val inputStream = targetCtx.resources.assets.open(filename)
inputStream.readBytes()
}
// NOTE: when not using the test orchestrator to separate tests, this tests may fail
// because the execution shows the same behavior as with targetContext
@Test(expected = NullPointerException::class)
fun picassoFailsWithNullPointerExceptionWhenUsingContext() {
val uri = Uri.parse("file:///android_asset/$filename")
// synchronous image loading cannot be performed in main thread
val context = InstrumentationRegistry.getInstrumentation().context
try {
Picasso.with(context)
.load(uri)
.get()
} catch (e: Exception) {
// sometimes Android Studio does not display callstacks in debugger
println(e.message)
throw e
}
}
@Test(expected = FileNotFoundException::class)
fun picassoFailsWithExceptionInAssetManagerOpenWhenUsingTargetContext() {
val uri = Uri.parse("file:///android_asset/$filename")
val context = InstrumentationRegistry.getInstrumentation().targetContext
try {
Picasso.with(context)
.load(uri)
.get()
} catch (e: Exception) {
// sometimes Android Studio does not display callstacks in debugger
println(e.message)
throw e
}
}
}
I observed the following:
-
The test
loadingAssetImageWithInstrumentationContextSucceeds
can load the asset while andloadingAssetImageWithInstrumentationTargetContextFails
cannot load the asset -- regardless of being run in an ActivityScenario or not. I conclude that the assets are stored in an APK accessible from context, but not in an APK accessible from targetContext. Unfortunately I know no way to look inside the referenced APKs since the Device File Explorer fails to open them in /data/app. -
The test
picassoFailsWithNullPointerExceptionWhenUsingContext
shows that usingInstrumentationRegistry.getInstrumentation().context
lets Picasso fail due to a null pointer exception. I could see in the Picasso builder internals that Picasso does not directly make use of the supplied context, but callsContextImpl::getApplicationContext
-- wheremPackageInfo.getApplication
isnull
:
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
-
The test
picassoFailsWithExceptionInAssetManagerOpenWhenUsingTargetContext
shows that usingInstrumentationRegistry.getInstrumentation().targetContext
will not cause a null pointer exception, but lead to aFileNotFoundException
from theAssetManager
as mentioned above. I conclude that the APK attached tot hattargetContext.applicationContext
does not contain the assets. -
I’m rather confused about all the different Context existing on created when spinning up Activities.
But coming back to the question:
There certainly are other (more expensive) ways to test my scenario, but I think there should be a working way to just load an asset with Picasso in an instrumented test.
Aucun commentaire:
Enregistrer un commentaire