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
Add the Hilt dependencies for the instrumentation test. Create an @AndroidEntryPoint activity to use as the container for the Fragment under test. Implement a utility class to launch the Fragment under test into the Hilt-enabled Activity.
A Hilt View Model is a Jetpack ViewModel that is constructor injected by Hilt. To enable injection of a ViewModel by Hilt use the @HiltViewModel annotation: @HiltViewModel public final class FooViewModel extends ViewModel { @Inject FooViewModel(SavedStateHandle handle, Foo foo) { // ... } }
You must annotate any UI test that uses Hilt with @HiltAndroidTest. This annotation is responsible for generating the Hilt components for each test. Also, you need to add the HiltAndroidRule to the test class. It manages the components' state and is used to perform injection on your test: // UI tests here.
You must set the generated Hilt test application to run in your instrumented tests or Robolectric tests as described in Test application. Note: Because HiltTestApplication_Application is code that Hilt generates at runtime, the IDE might highlight it in red until you run your tests.
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
package com.mypackage
@HiltViewModel
class MyViewModel @Inject constructor(
private val someDependency: MyType
) : ViewModel()
Then the generated module would look something like this:
@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";
}
}
}
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()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With