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() {}
}