与 Dagger 一起使用时,Espresso 生成 FileNotFoundException

Posted

技术标签:

【中文标题】与 Dagger 一起使用时,Espresso 生成 FileNotFoundException【英文标题】:Espresso generating FileNotFoundException, when used with Dagger 【发布时间】:2020-01-23 11:30:42 【问题描述】:

我一直在与一个遗留的 android 应用程序作斗争,试图为其添加测试和适当的架构。该应用程序有一个主要的LaunchActivity,它会在启动时运行一系列检查。最初,该活动使用 Dagger 来“注入依赖项”,该活动将用于运行检查。

我转向 MVVM,这样我就可以单独测试视图模型,无需插桩,并且只需要为 UI 测试注入一个模拟视图模型。我跟着this article 介绍了这些变化,包括切换到使用新的Dagger Android 方法,如AndroidInjection.inject

我希望测试能够尽可能多地指导任何更改,所以当我的基本架构正常工作时,我转而编写 UI 测试。现在,必须使用 Dagger 将模拟视图模型注入到活动中被证明是一项艰巨的任务,但我认为我已经找到了一个可行的解决方案。

我已经在使用 TestApp 和自定义检测运行程序来使用 DexOpener,我将其更改为也实现 HasActivityInjector,就像我的应用程序的实际自定义 App(两者都扩展 Application) .

对于 Dagger,我创建了单独的模块和一个用于测试的组件:

TestAppComponent

@Component(
        modules = [
            TestDepsModule::class,
            TestViewModelModule::class,
            TestAndroidContributorModule::class,
            AndroidSupportInjectionModule::class
        ]
)
@Singleton
interface TestAppComponent 
    @Component.Builder
    interface Builder 
        @BindsInstance
        fun application(application: Application): Builder

        fun testViewModelModule(testViewModelModule: TestViewModelModule): Builder

        fun build(): TestAppComponent
    

    fun inject(app: TestFieldIApp)

TestViewModelModule

@Module
class TestViewModelModule 
    lateinit var mockLaunchViewModel: LaunchViewModel

    @Provides
    fun bindViewModelFactory(factory: TestViewModelFactory): ViewModelProvider.Factory 
        return factory
    

    @Provides
    @IntoMap
    @ViewModelKey(LaunchViewModel::class)
    fun launchViewModel(): ViewModel 
        if(!(::mockLaunchViewModel.isInitialized)) 
            mockLaunchViewModel = mock(LaunchViewModel::class.java)
        
        return mockLaunchViewModel
    

TestAndroidConributorModule

@Module
abstract class TestAndroidContributorModule 
    @ContributesAndroidInjector
    abstract fun contributeLaunchActivity(): LaunchActivity

然后,在LaunchActivityTest,我有:

@RunWith(AndroidJUnit4::class)
class LaunchActivityTest 
    @Rule
    @JvmField
    val activityRule: ActivityTestRule<LaunchActivity> = ActivityTestRule(LaunchActivity::class.java, true, false)

    lateinit var viewModel: LaunchViewModel

    @Before
    fun init() 
        viewModel = mock(LaunchViewModel::class.java)

        val testApp: TestLegacyApp = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as TestLegacyApp

        val testViewModelModule: TestViewModelModule = TestViewModelModule()
        testViewModelModule.mockLaunchViewModel = viewModel

        DaggerTestAppComponent
                .builder()
                .application(testApp)
                .testViewModelModule(testViewModelModule)
                .build()
                .inject(testApp)
    

    @Test
    fun whenHideInstructionsIsFalse_showsInstructions() 
        `when`(viewModel.hideInstructions).thenReturn(false)

        activityRule.launchActivity(null)

        onView(withId(R.id.launch_page_slider)).check(matches(isDisplayed()))
        onView(withId(R.id.launch_progress_view)).check(matches(not(isDisplayed())))
    

    @Test
    fun whenHideInstructionsIsTrue_doesNotShowInstructions() 
        `when`(viewModel.hideInstructions).thenReturn(true)

        activityRule.launchActivity(null)

        onView(withId(R.id.launch_page_slider)).check(matches(not(isDisplayed())))
        onView(withId(R.id.launch_progress_view)).check(matches(isDisplayed()))
    

结果是视图模型被正确模拟,所以其他一切都应该工作......但是,当 Espresso 测试运行时,虽然测试显示它们已经通过,但有一个奇怪的堆栈跟踪,其中 (通过)视图断言应该是。

E/System: Unable to open zip file: /data/user/0/com.myapps.android.legacyapp/cache/qZb3CT3H.jar
E/System: java.io.FileNotFoundException: File doesn't exist: /data/user/0/com.myapps.android.legacyapp/cache/qZb3CT3H.jar
        at java.util.zip.ZipFile.<init>(ZipFile.java:215)
        at java.util.zip.ZipFile.<init>(ZipFile.java:152)
        at java.util.jar.JarFile.<init>(JarFile.java:160)
        at java.util.jar.JarFile.<init>(JarFile.java:97)
        at libcore.io.ClassPathURLStreamHandler.<init>(ClassPathURLStreamHandler.java:47)
        at dalvik.system.DexPathList$Element.maybeInit(DexPathList.java:702)
        at dalvik.system.DexPathList$Element.findResource(DexPathList.java:729)
        at dalvik.system.DexPathList.findResources(DexPathList.java:526)
        at dalvik.system.BaseDexClassLoader.findResources(BaseDexClassLoader.java:174)
        at java.lang.ClassLoader.getResources(ClassLoader.java:839)
        at java.util.ServiceLoader$LazyIterator.hasNextService(ServiceLoader.java:349)
        at java.util.ServiceLoader$LazyIterator.hasNext(ServiceLoader.java:402)
        at java.util.ServiceLoader$1.hasNext(ServiceLoader.java:488)
        at androidx.test.internal.platform.ServiceLoaderWrapper.loadService(ServiceLoaderWrapper.java:46)
        at androidx.test.espresso.base.UiControllerModule.provideUiController(UiControllerModule.java:42)
        at androidx.test.espresso.base.UiControllerModule_ProvideUiControllerFactory.provideUiController(UiControllerModule_ProvideUiControllerFactory.java:36)
        at androidx.test.espresso.base.UiControllerModule_ProvideUiControllerFactory.get(UiControllerModule_ProvideUiControllerFactory.java:26)
        at androidx.test.espresso.base.UiControllerModule_ProvideUiControllerFactory.get(UiControllerModule_ProvideUiControllerFactory.java:9)
        at androidx.test.espresso.core.internal.deps.dagger.internal.DoubleCheck.get(DoubleCheck.java:51)
        at androidx.test.espresso.DaggerBaseLayerComponent$ViewInteractionComponentImpl.viewInteraction(DaggerBaseLayerComponent.java:239)
        at androidx.test.espresso.Espresso.onView(Espresso.java:84)
        at com.myapps.android.legacyapp.tests.ui.launch.LaunchActivityTest.whenHideInstructionsIsFalse_showsInstructions(LaunchActivityTest.kt:64)

错误追踪到的LaunchActivityTest中的语句是:

onView(withId(R.id.launch_page_slider)).check(matches(isDisplayed()))

我无法弄清楚为什么测试会显示此错误。我知道这与 Dagger 有关,因为如果我注释掉构建 DaggerTestAppComponent,就没有问题。但是,如果不使用这个测试组件,我不确定如何将模拟视图模型注入到活动中。有些东西导致 Dagger 和 Espresso 不能很好地播放,我认为,与堆栈跟踪中的 DaggerBaseLayerComponent 有关。但我没有别的了。

我目前唯一的“解决方案”是切换到 Fragment 而不是 Activity,我可以在测试中完全跳过对 Dagger 的需求并遵循 this sample,但我真的很困惑为什么我会得到这个问题。我将不胜感激任何帮助找出原因。

【问题讨论】:

我发现这个问题发生在 Android 9 设备 (SGS 9+) 上,但不是在 Android 8.0.0 (Sony Xperia XZ2 Compact) 上。 您找到问题所在了吗? 问题解决了吗? 试试这个***.com/a/25276547/4592448 这有什么更新吗? 【参考方案1】:

这既不是基于非库存 ROM,也不是基于 Espresso 或 Dagger,而是已知的 issue,它似乎源于 androidx.test.runner.AndroidJUnitRunnerActivityScenarioFragmentScenario 的组合(如那里发布的堆栈跟踪可能会暗示)。

不使用ActivityTestRule 可能是当前解决此问题的唯一选择。只需在问题跟踪器上为问题加注星标,并在与问题相关的进展时收到通知。

【讨论】:

您的意思是说“不使用 ActivityScenario 可能是当前唯一的选择”?我对使用 ActivityScenario 或 ActivityTestRule 有什么可能解决此问题感到困惑?【参考方案2】:

奇怪的是,我有时通过卸载所有应用程序并在我的设备上测试相关内容或仅使用新的模拟器映像然后重新运行/安装所有内容来解决这个问题状态。我猜这与我的动态功能模块设置和 Android Studio 没有正确(重新)安装所有必要的 APK (其中资源 ID 可能在代码更改后重新洗牌?)有关。

【讨论】:

以上是关于与 Dagger 一起使用时,Espresso 生成 FileNotFoundException的主要内容,如果未能解决你的问题,请参考以下文章

@IntoMap @Binds 如何与 Dagger 一起工作?

使用 espresso 运行的 UIAutomator

dagger2简单使用与理解笔记

dagger2简单使用与理解笔记

Espresso:与 Play Store Popup 互动

dagger2的Qualifier与Scope