问题描述
我在搜索如何将ViewModel注入测试中进行了大量搜索,因此可以对其进行测试。可以说,视图模型具有一些业务逻辑交互器的构造函数注入。我可以轻松地将其注入片段,但是在测试中没有成功。
@HiltAndroidTest
class ViewModelTest
val randomViewmodel: RandomViewmodel// now what ? since by viewModels() is not accessible in tests
@Test
fun viewModelTet() {
randomViewmodel.triggerAction()
assertEquals(RandomVIewState(1),randomViewmodel.getState())
}
我试图在测试类中实现byViewModels(),并且可以在没有构造函数参数的情况下注入viewmodel,但无法成功实现。
class RandomViewmodel @ViewModelInject constructor(
private val randomInteractor: RandomInteractor
) : ViewModel
Caused by: java.lang.InstantiationException: class app.RandomViewModel has no zero argument constructor
原因:我希望能够完全测试我的屏幕逻辑,因为viewModel可以处理交互器等方面的依赖关系。后面可能有很多逻辑,各种数据在周围流动。测试片段很有可能,但是在进行大量测试的情况下,测试片段的速度会变慢。
我已经读过https://developer.android.com/jetpack/guide#test-components,这建议您进行JUnit测试并在viewModel中模拟依赖项,但是您必须分别为每个依赖项创建测试,并且不能真正测试整个屏幕的逻辑
解决方法
@HiltViewModel
注释会生成您本来可以编写的绑定模块。
其中之一是名为 BindsModule 的模块。 此类在包含该多绑定模块和键的一个包装器类中声明。
例如,假设您创建了一个名为 MyViewModel
package com.mypackage
@HiltViewModel
class MyViewModel @Inject constructor(
private val someDependency: MyType
) : ViewModel()
然后生成的模块看起来像这样:
@OriginatingElement(
topLevelClass = MyViewModel.class
)
public final class MyViewModel_HiltModules {
private MyViewModel_HiltModules() {
}
@Module
@InstallIn(ViewModelComponent.class)
public abstract static class BindsModule {
private BindsModule() {
}
@Binds
@IntoMap
@StringKey("com.mypackage.MyViewModel")
@HiltViewModelMap
public abstract ViewModel binds(MyViewModel vm);
}
@Module
@InstallIn(ActivityRetainedComponent.class)
public static final class KeyModule {
private KeyModule() {
}
@Provides
@IntoSet
@HiltViewModelMap.KeySet
public static String provide() {
return "com.mypackage.MyViewModel";
}
}
}
因此,您的 ViewModel 可以通过简单地在测试类中与实现类型匹配的属性上使用 @Binds
注释来替换该 @BindValue
合同,在这种情况下,它将是 MyViewModel
.
无需卸载任何与 ViewModel 相关的模块。
@HiltAndroidTest
class MyFragmentInstrumentedUnitTest {
@get:Rule val hiltRule = HiltAndroidRule(this)
// either a subclass or a mock,as long as the types match
// it will provide this instance as the implementation of the abstract binding
// `public abstract ViewModel binds(MyViewModel vm);`
@BindValue
val mockMyViewModel= mock<MyViewModel>()
@Before
fun init() {
hiltRule.inject()
}
}
,
实际上,您根本不需要注入viewModel。这应该工作
class ViewModelTest {
// Your specific ViewModel
private lateinit var yourViewModel: ViewModel
// for your repositories that use async task such as coroutines etc.
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Before
fun setUp() {
yourViewModel = ViewModel( // Dependencies you need and stuff )
}
@Test
fun `testing_stuff`() {
val value = yourViewModel.testingFunction("Bla bla")
assertThat(value).isEqualTo("Bla bla")
}
}
dependencies {
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
}