Это мой тестовый класс MWE, который зависит от AndroidX, JUnit 4 и 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() {}
}
Примечание: метод защищен суперклассом ViewModel.
Я хочу убедиться, что MyViewModel#onCleared вызывает Object#function. В приведенном выше коде это достигается путем отражения. У меня вопрос: могу ли я как-то запустить или смоделировать систему Android, чтобы был вызван метод onCleared, чтобы мне не понадобилось отражение?
Из onCleared JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
Другими словами, как мне создать эту ситуацию, чтобы я знал, что вызывается onCleared, и мог проверить его поведение?
В этом ответе Robolectric используется для того, чтобы фреймворк Android вызывал onCleared на вашем ViewModel. Этот способ тестирования медленнее, чем использование отражения (как в вопросе), и зависит как от Robolectric, так и от платформы Android. Этот компромисс зависит от вас.
... вы можете видеть, что ViewModel#onCleared вызывается только в ViewModelStore (для вашего собственного ViewModels). Это класс хранения для моделей представлений и принадлежит классам ViewModelStoreOwner, например. FragmentActivity. Итак, когда ViewModelStore вызывает onCleared на вашем ViewModel?
В нем должен храниться ваш ViewModel, затем его нужно очистить (что вы не можете сделать самостоятельно).
Ваша модель представления сохраняется в ViewModelProvider, когда вы get на ViewModel с помощью ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass), где T - это класс вашей модели представления. Он хранит его в ViewModelStoreFragmentActivity.
Хранилище ясно, например, когда ваша активность фрагмента уничтожена. Это куча связанных вызовов, которые встречаются повсюду, но в основном это:
FragmentActivity.ViewModelProvider, используя ViewModelProviders#of.ViewModel с помощью ViewModelProvider#get.Теперь onCleared должен быть вызван в вашей модели представления. Давайте протестируем это с помощью Robolectric 4, JUnit 4, MockK 1.9:
@RunWith(RobolectricTestRunner::class) в свой тестовый класс.Robolectric.buildActivity(FragmentActivity::class.java)setup на контроллере, это позволяет его уничтожить.get.destroy на контроллере.onCleared.... на основе примера вопроса:
@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() {}
}
Я только что создал это расширение для 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
}
Это зависит от ViewModelStore#clear для вызова ViewModel#onCleared, и поэтому этот вызов несколько скрыт среди этих строк кода. Тем не менее, это решение довольно хорошее из-за его краткости, отсутствия необходимости в отражении или тестах Robolectric / Android, а также того факта, что на Android обычно вызов выполняется экземпляром ViewModelStore. Спасибо за Ваш ответ!
В kotlin вы можете переопределить защищенную видимость с помощью public, а затем вызвать ее из теста.
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
Немного @RestrictTo(RestrictTo.Scope.TESTS) поверх onCleared() и все хорошо
Для Java, если вы создаете свой тестовый класс в том же пакете (в тестовом каталоге), что и класс ViewModel (здесь MyViewModel), то вы можете вызвать метод onCleared из тестового класса; поскольку защищенные методы также являются частными для пакета.
Да, на Java так работает. Но я не использую Java.
Да, я знаю, это для пользователей, которые ищут решение на Java; поскольку в названии конкретно не упоминается «в Котлине».
Вы могли бы
public override fun onCleared(), но это раскрывает метод, который не подходит, так как метод должен вызываться только системой Android.