Testing Android ViewModelProvider with a mock ViewModel - android

I'm excited to use the new Android Architecture Components ViewModel system, which nicely separates the Activity/Fragment/Layout rendering concerns from the ViewModel logic. I've successfully unit-tested the ViewModel in isolation, and now would like to try out some screenshot testing by providing mocked ViewModels to the Activity/Fragment for various state scenarios.
I've successfully configured my androidTests to be able to use Mockito in my device tests, that part works great.
However, the officially recommended way of calling ViewModelProvider or delegating by viewModels<> does not seem to offer a way to inject mocked ViewModels. I'd rather not add an entire DI framework just to work around this omission in the documentation, so I wonder if anyone has any successful examples of providing mocked ViewModels with the official Android Architecture Components without the extra dependencies of Dagger or Hilt.
The only related answer from 1 year ago suggests using ActivityTestRule and manually controlling the activity lifecycle, but that Rule is deprecated in favor of activityScenarioRule which does not provide this control.

You can use a ViewModelProvider, so you can replace the ViewModelProvider.Factory in the tests with a mock. For example by using:
viewModel = ViewModelProvider(this, ViewModelFactoryOfFactory.INSTANCE)
.get(MyViewModel::class.java)
Where:
object ViewModelFactoryOfFactory {
// The default factory.
var INSTANCE: ViewModelProvider.Factory = MyViewModelFactory()
private set
// To set the factory during tests.
#VisibleForTesting
fun setTestFactory(factory: ViewModelProvider.Factory) {
ViewModelFactoryOfFactory.INSTANCE = factory
}
}
Then in the tests setup one can:
ViewModelFactoryOfFactory.setTestFactory(mockFactory)
One may argue that all this could be replaced by just the factory to get the ViewModel.
Another option could be just make the ViewModelProvider.Factory a field/property in the Activity or fragment, so it can be also set from tests, also allowing for better memory management.

I decided to rewrite the by viewModels delegate to check for instances in a map of mock ViewModels, so my activities can use the normal delegate pattern and provide their own factories if the ViewModel isn't found.
val mockedViewModels = HashMap<Class<*>, ViewModel>()
#MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
// the production producer
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return createMockedViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
/// ... and similar for the fragment-ktx delegates
/**
* Wraps the default factoryPromise with one that looks in the mockedViewModels map
*/
fun <VM : ViewModel> createMockedViewModelLazy(
viewModelClass: KClass<VM>,
storeProducer: () -> ViewModelStore,
factoryPromise: () -> ViewModelProvider.Factory
): Lazy<VM> {
// the mock producer
val mockedFactoryPromise: () -> ViewModelProvider.Factory = {
// if there are any mocked ViewModels, return a Factory that fetches them
if (mockedViewModels.isNotEmpty()) {
object: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return mockedViewModels[modelClass] as T
?: factoryPromise().create(modelClass) // return the normal one if no mock found
}
}
} else {
// if no mocks, call the normal factoryPromise directly
factoryPromise()
}
}
return ViewModelLazy(viewModelClass, storeProducer, mockedFactoryPromise)
}

Related

Provide domain-layer UseCase classes with HILT

I am implementing some of the architectural designs from Google I/O's app to my own app, but I have come across something in their app that has created some confusion for me.
They have a domain layer with repository usecases, which I use myself in my apps usually. However, I do have to provide these usecases with dagger in my app. But on Google's I/O app, I cannot find any module that provides these usecases. And when used with viewmodels annotated #HiltViewModel (In my own app), it seems like it works? Somehow these get injected into my viewmodels. I do have to provide all the usecase's dependencies(repositories etc) with Hilt, but I don't have to provide any usecase via Hilt.
Here is example how it looks in my code.
Usecase:
abstract class UseCase<in P, R>(private val coroutineDispatcher: CoroutineDispatcher) {
suspend operator fun invoke(parameters: P): Resource<R> {
return try {
withContext(coroutineDispatcher) {
execute(parameters).let {
Resource.Success(it)
}
}
} catch (e: Exception) {
Timber.d(e)
Resource.Error(e.toString())
}
}
#Throws(RuntimeException::class)
protected abstract suspend fun execute(parameters: P): R
}
Concrete implementation of usecase:
class GetListUseCase #Inject constructor(
private val coroutineDispatcher: CoroutineDispatcher,
private val remoteRepository: RemoteRepository
): UseCase<ListRequest,ItemsList>(coroutineDispatcher) {
override suspend fun execute(parameters: ListRequest): ItemsList{
return remoteRepository.getList(parameters)
}
}
Viewmodel:
#HiltViewModel
class DetailViewModel #Inject constructor(
private val GetListUseCase: getListUseCase
): ViewModel() {
suspend fun getList(): Resource<ItemsList> {
getPokemonListUseCase.invoke(ListRequest(3))
}
}
Example of provided repo:
#Singleton
#Provides
fun provideRemoteRepository(
api: Api
): RemoteRepository = RemoteRepositoryImpl(api)
Remote repo:
#ActivityScoped
class RemoteRepositoryImpl #Inject constructor(
private val api: Api
): RemoteRepository {
override suspend fun getList(request: ListRequest): PokemonList {
return api.getPokemonList(request.limit, request.offset)
}
}
My Question is:
How come this works? Why don't I have to provide usecase' via Hilt? Or is my design wrong and I should provide usecases via Hilt even if this works?
EDIT:
Link to Google I/O domain layer and ui layer if it helps.
Your design is great! And it's not wrong, but yes, you can add some additional context for your use cases if you put them to a separate module and scope them per your needs. Modules exist mostly for cases when you do have your third-party dependencies (the simplest example is OkHTTPClient) or when you have interface -> impl of the interface, or you want to visibly limit/extend lifecycle/visibility of your components.
Currently you're telling Hilt how to provide instances of GetListUseCase by annotating its constructor with #Inject AND Hilt already knows what is "CoroutineDispatcher" (because it's been provided by #Provides in the coroutine module, right?) and what is RemoteRepository (because you're injecting the interface, but beneath the hood you're providing the real implementation of it in the repo module by #Provides).
So it's like saying - give me an instance of the use case class with two constructor dependencies, and both of them are known to Hilt, so it's not confusing.
And if you want to have a scoped binding/component (like explained here) or to mark your use case as a singleton, then you have to create a UseCaseModule and scope your use case components there.

How to create separate ViewModels per list item when using Compose UI?

I'm working on a trading app. I need to list the user stocks and their value (profit or loss) among with the total value of the portfolio.
For the holdings list, in an MVP architecture I would create a presenter for each list item but for this app I decided to use MVVM (Compose, ViewModels and Hilt ). My first idea was to create a different ViewModel for each list item. I'm using hiltViewModel() in the composable method signature to create instances of my ViewModel, however this gives me always the same instance and this is not what I want. When using MVVM architecture, is what I'm trying to do the correct way or I should use a single ViewModel? Are you aware about any project I could have a look at? The image below is a super simplification of my actual screen, each cell is complex and that's why I wanted to use a different ViewModel for each cell. Any suggestion is very welcome.
Hilt doesn't support keyed view models. There's a feature request for keyed view models in Compose, but we had to wait until Hilt supports it.
Here's a hacky solution on how to bypass it for now.
You can create a plain view model, which can be used with keys, and pass injections to this view model through Hilt view model:
class SomeInjection #Inject constructor() {
val someValue = 0
}
#HiltViewModel
class InjectionsProvider #Inject constructor(
val someInjection: SomeInjection
): ViewModel() {
}
class SomeViewModel(private val injectionsProvider: InjectionsProvider) : ViewModel() {
val injectedValue get() = injectionsProvider.someInjection.someValue
var storedValue by mutableStateOf("")
private set
fun updateStoredValue(value: String) {
storedValue = value
}
}
#Composable
fun keyedViewModel(key: String) : SomeViewModel {
val injectionsProvider = hiltViewModel<InjectionsProvider>()
return viewModel(
key = key,
factory = object: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
#Suppress("UNCHECKED_CAST")
return SomeViewModel(injectionsProvider) as T
}
}
)
}
#Composable
fun TestScreen(
) {
LazyColumn {
items(100) { i ->
val viewModel = keyedViewModel("$i")
Text(viewModel.injectedValue.toString())
TextField(value = viewModel.storedValue, onValueChange = viewModel::updateStoredValue)
}
}
}
Unfortunately, HiltViewModelFactory is not a KeyedFactory. So as of now it does not support same viewModel with multiple instances.
Tracking: https://github.com/google/dagger/issues/2328
You have to use Dagger version 2.43 (or newer), it includes the feature/fix to support keys in Hilt ViewModels
https://github.com/google/dagger/releases/tag/dagger-2.43
From the release description:
Fixes #2328 and #3232 where getting multiple instances of #HiltViewModel with different keys would cause a crash.

best way for injecting ViewModels?

I have used koin in the past and injecting viewModel with koin is a single liner. I need to know how to do that without it!
should We use a big switch/case in ViewModelFactory for different viewmodels?
class ViewModelFactory: ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return when(modelClass) {
is FirstViewModel -> FirstViewModel()
is SecondViewModel -> SecondViewModel()
...
}
}
}
but then I have to inject all the dependencies of all viewmodels to the factory. and that's really messy code. even without that the switch/case by itself is messy! I don't think you should do that specially in big projects. so what are the alternative ways of doing this?
how can dagger help with this?
there are actually quite good alternatives to that.
first: the first one which people tend to forget about is having a ViewModelfactory per ViewModel it's a lot better than having a massive factory. that is what we call the Single Responsibility Principle
class FirstViewModelFactory(val firstDependency: SomeClass, val secondDependency: SomeOtherClass): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
FirsViewModel(firstDependency, secondDependency) as T
}
}
and in the activity:
val viewModel: FirstViewModel = ViewModelProvider(this, FirstViewModelFactory(first, second))[FirstViewModel::class.java]
Second: if you want to have only one Factory for all your ViewModels you can define a generic factory with that takes a lambda:
class ViewModelFactory<VM: ViewModel>(val provider: () -> VM): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return provider() as T
}
}
and use it like this in the activity or fragment:
val ViewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
FirstViewModel(first, second)
})[FirstViewModel::class.java]
this actually makes your life a lot easier. but it can still be improved
Third: you can tell dagger how to provide you FirstViewModel and its dependencies.
if you don't know how to use dagger, you try to learn it then read this part.
you need to tell dagger that you want to get an instance of FirstViewModle. the place for that is in the AppComponent.
#Component(modules = [AppModule::class])
interface AppComponent {
val applicationContext: Context
val firstViewModel: FirstViewModel
...
#Component.Factory
interface Factory {
fun create(#BindsInstance applicationContext: Context): AppComponent
}
}
and in your appModule, you need to tell dagger how to provide dependencies of FirstViewModel with #Provides annotation. then you need to instantiate your dagger component in application class, there are lots of ways to do this, I just use the factory interface:
class MyApplication: Application() {
val component: AppComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
}
don't forget to add MyApplication in the manifest.
then in your activity, you can inject the viewModel without ever being worry about the dependencies:
val appComponent = (application as MyApplication).appComponent
val viewModel: FirstViewModel = ViewModelProvider(this, ViewModelFactory<FirstViewModel>{
appComponent.firstViewModel
})[FirstViewModel::class.java]
you can make it more beautiful and readable using lazy and extension functions and you can eventually have something like this:
private val viewModel: FirstViewModel by injectVmWith { appInjector.firstViewModel }
Fourth: you can always use dagger's multibinding. with this, you inject ViewModelFactory to activity or fragment and you retrieve the viewModel from that. with this, you are telling dagger to put all your viewModels in a map and inject it to the ViewModelFactory. you help dagger to find viewModels by annotating them. and you also tell dagger what their key is using another annotation. you do all that in another module and for every viewmodel you need a function in your viewModel module. then instead of switch/case in your massive factory, you tell dagger to get the required viewmodel from the map based on its type. it's a service locator (anti?)pattern. it's another topic by itself and this answer is already too long. refer to this or this
Summary : I think if you are using dagger the third one is definitely the winner. multibinding is also good but every time you add a viewmodel you need to remember to add a function in the viewmodelModule too(just like Koin). and if you are not using dagger, the second way in my opinion is the best but you should decide what is best for you.
Koin is also great!

How to share same instance of ViewModel between Activities using Koin DI?

I am using Koin library in Kotlin for DI
Koin providing by viewmodel() for get instance of ViewModel by sharedViewModel() to get same instance in fragments.
How can I get same instance of the ViewModel in activities ? I didn't find any way to achieve this.
you must use single{} instead of viewModel{} in module declaration.
single { SharedViewModel() }
And, you can use viewModel() in your views.
View1
private val viewModel: SharedViewModel by viewModel()
View2
private val viewModel: SharedViewModel by viewModel()
But you must load modules when view start by
loadKoinModules(module1)
The important point is that you must unload module in when destroy view.
unloadKoinModules(mainModule)
So, when unload modules your singleton ViewModel will be destroyed.
#EDIT
Now, you can use sharedViewModel declaration.
After some research or discussion on architecture level and also report and issue github Koin,i found solution for this
In this scenario,We should save that state/data into Repository which we need to share between multiple activities not in the viewModel and two or more different ViewModels can access same state/data that are saved in single instance of repository
you need to read more about ViewModel to understand it better.
https://developer.android.com/topic/libraries/architecture/viewmodel
ViewModel is connected to your Activity
so you can share your Activities ViewModel only between his Fragments ,
that is what mean sharedViewModel in koin
sharedViewModel is the same if you use ViewModel Factory with same context .
sharing any data between Activities can be done via Intent , there is no another way in Android,
or you can keep some static / global data and share it between Activities
I would suggest making the app a ViewModelStoreOwner and injecting the viewModels using as owner the app.
The code required would look like this
class App : Application(), ViewModelStoreOwner {
private val mViewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore {
return mViewModelStore
}
}
You can define some extensions to easily inject the viewModels
val Context.app: App
get() = applicationContext as App
inline fun <reified T : ViewModel> Context.appViewModel(
qualifier: Qualifier? = null,
noinline state: BundleDefinition? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) {
GlobalContext.get().getViewModel(qualifier, state, { ViewModelOwner.from(app, null) }, T::class, parameters)
}
}
inline fun <reified T : ViewModel> Fragment.appViewModel(
qualifier: Qualifier? = null,
noinline state: BundleDefinition? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> {
return lazy(LazyThreadSafetyMode.NONE) {
GlobalContext.get().getViewModel(qualifier, state, { ViewModelOwner.from(requireContext().app, null) }, T::class, parameters)
}
}
You can then inject your viewModel like this
class MainActivity : AppCompatActivity() {
private val mAppViewModel: AppViewModel by appViewModel()
}
The advantage of this solution is that you don't need to recreate the view model and if you decide to save the state between app restarts, you can easily make the app an SavedStateRegistryOwner as well and using the SavedStateHandle save/restore your state from inside the viewModel, being now bound to the process lifecycle.
I know this is very very late but you can try this:
if you are extending a baseviewmodel, you need to declare the baseViewmodel as a single then in your respective activity inject the BaseViewModel.
Practical example:
val dataModule = module {
single { BaseViewModel(get(), get()) }
}
in your ViewModel
class LoginViewModel(private val param: Repository,
param1: Pref,
param2: Engine) : BaseViewModel(param1, param2)
Then in your activity class
val baseViewModel: BaseViewModel by inject()
Hope this help someone.

How to ensure ViewModel#onCleared is called in an Android unit test?

This is my MWE test class, which depends on AndroidX, JUnit 4 and MockK 1.9:
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
MyViewModel::class.members
.single { it.name == "onCleared" }
.apply { isAccessible = true }
.call(MyViewModel())
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
Note: the method is protected in superclass ViewModel.
I want to verify that MyViewModel#onCleared calls Object#function. The above code accomplished this through reflection. My question is: can I somehow run or mock the Android system so that the onCleared method is called, so that I don't need reflection?
From the onCleared JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
So, in other words, how do I create this situation so that I know onCleared is called and I can verify its behaviour?
In kotlin you can override the protected visibility using public and then call it from a test.
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
I've just created this extension to ViewModel:
/**
* Will create new [ViewModelStore], add view model into it using [ViewModelProvider]
* and then call [ViewModelStore.clear], that will cause [ViewModel.onCleared] to be called
*/
fun ViewModel.callOnCleared() {
val viewModelStore = ViewModelStore()
val viewModelProvider = ViewModelProvider(viewModelStore, object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = this#callOnCleared as T
})
viewModelProvider.get(this#callOnCleared::class.java)
//Run 2
viewModelStore.clear()//To call clear() in ViewModel
}
TL;DR
In this answer, Robolectric is used to have the Android framework invoke onCleared on your ViewModel. This way of testing is slower than using reflection (like in the question) and depends on both Robolectric and the Android framework. That trade-off is up to you.
Looking at Android's source...
...you can see that ViewModel#onCleared is only called in ViewModelStore (for your own ViewModels). This is a storage class for view models and is owned by ViewModelStoreOwner classes, e.g. FragmentActivity. So, when does ViewModelStore invoke onCleared on your ViewModel?
It has to store your ViewModel, then the store has to be cleared (which you cannot do yourself).
Your view model is stored by the ViewModelProvider when you get your ViewModel using ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass), where T is your view model class. It stores it in the ViewModelStore of the FragmentActivity.
The store is clear for example when your fragment activity is destroyed. It's a bunch of chained calls that go all over the place, but basically it is:
Have a FragmentActivity.
Get its ViewModelProvider using ViewModelProviders#of.
Get your ViewModel using ViewModelProvider#get.
Destroy your activity.
Now, onCleared should be invoked on your view model. Let's test it using Robolectric 4, JUnit 4, MockK 1.9:
Add #RunWith(RobolectricTestRunner::class) to your test class.
Create an activity controller using Robolectric.buildActivity(FragmentActivity::class.java)
Initialise the activity using setup on the controller, this allows it to be destroyed.
Get the activity with the controller's get method.
Get your view model with the steps described above.
Destroy the activity using destroy on the controller.
Verify the behaviour of onCleared.
Full example class...
...based on the question's example:
#RunWith(RobolectricTestRunner::class)
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
val controller = Robolectric.buildActivity(FragmentActivity::class.java).setup()
ViewModelProviders.of(controller.get()).get(MyViewModel::class.java)
controller.destroy()
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
For Java, if you create your test class in the same package (within the test directory) as of the ViewModel class (here, MyViewModel), then you can call onCleared method from the test class; since protected methods are also package private.

Categories

Resources