Voici ma classe de test MWE, qui dépend d'AndroidX, JUnit 4 et 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() {}
}
Remarque: la méthode est protégée dans la superclasse ViewModel .
/ p>
Je veux vérifier que MyViewModel # onCleared appelle Object # function . Le code ci-dessus a accompli cela par réflexion. Ma question est la suivante: puis-je exécuter ou me moquer du système Android pour que la méthode onCleared soit appelée, de sorte que je n'ai pas besoin de réflexion?
À partir de onCleared JavaDoc:
Cette méthode sera appelée lorsque ce ViewModel n'est plus utilisé et sera détruit.
Alors, en d'autres termes, comment créer cette situation pour que je sache que onCleared est appelé et que je puisse vérifier son comportement?
3 Réponses :
Dans cette réponse, Robolectric est utilisé pour que le framework Android appelle onCleared sur votre ViewModel . Cette méthode de test est plus lente que l'utilisation de la réflexion (comme dans la question) et dépend à la fois de Robolectric et du framework Android. Ce compromis dépend de vous.
... vous pouvez voir que ViewModel # onCleared n'est appelé que dans ViewModelStore (pour vos propres ViewModels ). Il s'agit d'une classe de stockage pour les modèles de vue et appartient aux classes ViewModelStoreOwner , par exemple FragmentActivity . Alors, quand est-ce que ViewModelStore appelle onCleared sur votre ViewModel?
Il doit stocker votre ViewModel code>, alors le magasin doit être effacé (ce que vous ne pouvez pas faire vous-même).
Votre modèle de vue est stocké par le ViewModelProvider lorsque vous obtenez votre ViewModel en utilisant ViewModelProviders.of (FragmentActivity activity) .get (Class , où T est votre classe de modèle de vue. Il le stocke dans le ViewModelStore du FragmentActivity.
Le magasin est vide par exemple lorsque votre activité de fragment est détruite. C'est un tas d'appels enchaînés qui vont partout, mais en gros c'est:
FragmentActivity . ViewModelProvider en utilisant ViewModelProviders # of . ViewModel à l'aide de ViewModelProvider # get . Maintenant, onCleared devrait être appelé sur votre modèle de vue. Testons-le en utilisant Robolectric 4, JUnit 4, MockK 1.9:
@RunWith (RobolectricTestRunner :: class) à votre classe de test. Robolectric.buildActivity(FragmentActivity::class.java) setup sur le contrôleur, cela permet de la détruire. get du contrôleur. destroy sur le contrôleur. onCleared . ... basé sur l'exemple de la question:
@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() {}
}
Je viens de créer cette extension pour 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
}
Cela dépend de ViewModelStore # clear pour appeler ViewModel # onCleared , et donc cet appel est quelque peu caché parmi ces lignes de code. Cependant, cette solution est plutôt intéressante de par sa brièveté, pas besoin de réflexion ni de tests Robolectric / Android, et le fait que sur Android, c'est normalement une instance de ViewModelStore qui effectue l'appel. Merci pour votre réponse!
Dans kotlin, j'ai trouvé que je pouvais remplacer la visibilité protégée en utilisant public , puis je peux l'appeler à partir d'un test.
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
Un peu de @RestrictTo (RestrictTo.Scope.TESTS) en plus de onCleared () et tout va bien
Vous pouvez
public override fun onCleared (), mais cela expose la méthode qui n'est pas bonne, car la méthode ne doit être appelée que par le système Android.