我一直在寻找如何将 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() ,并且可以在没有构造函数参数的情况下注入视图模型,但没有成功。
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
的 ViewModel
编辑:
从 Dagger 2.51.x 开始,ViewModel 绑定现在使用
@LazyClassKey
而不是 @StringKey
package com.mypackage
@HiltViewModel
class MyViewModel @Inject constructor(
private val someDependency: MyType
) : ViewModel()
然后生成的模块将如下所示:
@Generated("dagger.hilt.android.processor.internal.viewmodel.ViewModelProcessor")
@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
@LazyClassKey(MyViewModel.class)
@HiltViewModelMap
public abstract ViewModel binds(MyViewModel vm);
}
@Module
@InstallIn(ActivityRetainedComponent.class)
public static final class KeyModule {
private KeyModule() {
}
@Provides
@IntoMap
@LazyClassKey(MyViewModel.class)
@HiltViewModelMap.KeySet
public static boolean provide() {
return true;
}
}
}
因此,您的 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()
}
}