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