Эспрессо, генерирующее FileNotFoundException, при использовании с Dagger - PullRequest
1 голос
/ 23 сентября 2019

Я боролся с устаревшим приложением для Android, пытаясь добавить к нему тестирование и правильную архитектуру.Приложение имеет основной LaunchActivity, который запускает серию проверок при запуске.Первоначально, деятельность использовала Dagger, чтобы довольно слабо «внедрить зависимости», которые действие использовало бы для выполнения проверок.

Я переключил механизмы на MVVM, чтобы я мог тестировать модель представления отдельно, без инструментов,и нужно будет только внедрить модель макета представления для тестов пользовательского интерфейса.Я следовал этой статье , чтобы представить изменения, в том числе перейти на использование новых методов Dagger Android, таких как AndroidInjection.inject.

. Я хочу, чтобы тесты давали как можно больше изменений, поэтомукогда у меня работала базовая архитектура, я переключился на написание тестов пользовательского интерфейса.Теперь необходимость внедрить модель фиктивного представления в действие с 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()))

Я не могу понять, почему тест показывает эту ошибку.Я знаю, что это связано с Кинжалом, потому что, если я закомментирую здание DaggerTestAppComponent, это не проблема.Но, не используя этот тестовый компонент, я не уверен, как я могу внедрить модель макета представления в действие.Что-то заставляет Dagger и Espresso не играть хорошо, я думаю, что-то связанное с этим DaggerBaseLayerComponent в трассировке стека.Но у меня больше ничего нет.

Единственное «решение», которое у меня есть в настоящее время, - это переключение на Фрагмент вместо Деятельности, где я мог бы вообще пропустить необходимость в Кинжале в тестах и ​​следовать этому примеру , но я действительно сбит с толку относительно того, почему я получаю эту проблему.Я был бы очень признателен за любую помощь в выяснении причины.

...