I was searching quite a lot for how to inject ViewModel into tests so I can test it. Lets say the viewmodel have a constructor injection with some business logic interactor. I can inject it into fragments easily but no success in tests.
@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())
}
I tried to implement byViewModels() in test class and could inject viewmodel without constructor arguments but no success with them.
class RandomViewmodel @ViewModelInject constructor(
private val randomInteractor: RandomInteractor
) : ViewModel
Caused by: java.lang.InstantiationException: class app.RandomViewModel has no zero argument constructor
Reason: I want to be able to fully test my screen logic since the viewModel will handle the dependencies on interactors etc. there might be quite a lot of logic behind with various data flowing around. Testing the fragment would be most likely possible but way slower in a larger poject with a lot of tests.
I already read the https://developer.android.com/jetpack/guide#test-components , which is suggesting doing JUnit tests and mocking the dependencies in viewModel but then you have to create tests for each dependency separatelly and cant really test the logic for the whole screen
The @HiltViewModel
annotation, generates binding Modules that you would have otherwise written.
One of those being a module called BindsModule. This class is declared inside of a wrapper class that contains that multi-binding module as well as one for the key.
For example, let's say you have created a ViewModel called MyViewModel
Edit:
As of Dagger 2.51.x, ViewModel Bindings now use @LazyClassKey
instead of @StringKey
package com.mypackage
@HiltViewModel
class MyViewModel @Inject constructor(
private val someDependency: MyType
) : ViewModel()
Then the generated module would look something like this:
@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;
}
}
}
Therefore your ViewModel can replace that @Binds
contract by simply using the @BindValue
annotation on a property in your test class that matches the implementation type, in this case, it would be MyViewModel
.
No need to uninstall any modules related to the 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()
}
}