Как убедиться, что ViewModel # onCleared вызывается в модульном тесте Android? - PullRequest
0 голосов
/ 09 января 2019

Это мой тестовый класс 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:

Этот метод будет вызываться, когда эта ViewModel больше не используется и будет уничтожена.

Итак, другими словами, как мне создать эту ситуацию, чтобы я знал, что вызывается onCleared, и я могу проверить его поведение?

Ответы [ 2 ]

0 голосов

Я только что создал это расширение для 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
}
0 голосов
/ 10 января 2019

TL; DR

В этом ответе Robolectric используется для вызова платформы Android onCleared на вашем ViewModel. Этот способ тестирования медленнее, чем использование отражения (как в вопросе), и зависит как от Robolectric, так и от платформы Android. Этот компромисс зависит от вас.


Глядя на источник Android ...

... вы можете видеть, что ViewModel#onCleared вызывается только в ViewModelStore (для вашего ViewModels). Это класс хранения для моделей представлений, который принадлежит ViewModelStoreOwner классам, например, FragmentActivity. Итак, когда ViewModelStore вызывает onCleared на вашем ViewModel?

Он должен хранить ваше ViewModel, затем магазин должен быть очищен (что вы не можете сделать самостоятельно).

Ваша модель вида сохраняется в ViewModelProvider, когда вы get ваш ViewModel, используя ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass), где T - ваш класс модели представления. Он хранит его в ViewModelStore из FragmentActivity.

Магазин очищается, например, когда ваша активность фрагмента разрушена. Это связка звонков, которые идут повсюду, но в основном это:

  1. Есть FragmentActivity.
  2. Получите ViewModelProvider, используя ViewModelProviders#of.
  3. Получите ViewModel, используя ViewModelProvider#get.
  4. Уничтожь свою деятельность.

Теперь onCleared должно быть вызвано на вашей модели представления. Давайте проверим это, используя Robolectric 4, JUnit 4, MockK 1.9:

  1. Добавьте @RunWith(RobolectricTestRunner::class) в свой тестовый класс.
  2. Создать контроллер активности, используя Robolectric.buildActivity(FragmentActivity::class.java)
  3. Инициализируйте действие, используя setup на контроллере, это позволяет уничтожить его.
  4. Получить действие с помощью метода контроллера get.
  5. Получите вашу модель представления с шагами, описанными выше.
  6. Уничтожить действие, используя destroy на контроллере.
  7. Проверьте поведение 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() {}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...