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.