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 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.
...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:
FragmentActivity
.ViewModelProvider
using ViewModelProviders#of
.ViewModel
using ViewModelProvider#get
.Now, onCleared
should be invoked on your view model. Let's test it using Robolectric 4, JUnit 4, MockK 1.9:
@RunWith(RobolectricTestRunner::class)
to your test class.Robolectric.buildActivity(FragmentActivity::class.java)
setup
on the controller, this allows it to be destroyed.get
method.destroy
on the controller.onCleared
....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() {}
}