How to ensure ViewModel#onCleared is called in an Android unit test?

android viewmodel unit test example
android viewmodel oncleared
mock viewmodel android
shared viewmodel android
android invalidate viewmodel
android by viewmodels
android viewmodel button click
android viewmodel scope

This is my MWE test class, which depends on AndroidX, JUnit 4 and 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() {}
}

Note: the method is protected in superclass ViewModel.

I want to verify that MyViewModel#onCleared calls Object#function. The above code accomplished this through reflection. My question is: can I somehow run or mock the Android system so that the onCleared method is called, so that I don't need reflection?

From the onCleared JavaDoc:

This method will be called when this ViewModel is no longer used and will be destroyed.

So, in other words, how do I create this situation so that I know onCleared is called and I can verify its behaviour?

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:

  1. Have a FragmentActivity.
  2. Get its ViewModelProvider using ViewModelProviders#of.
  3. Get your ViewModel using ViewModelProvider#get.
  4. Destroy your activity.

Now, onCleared should be invoked on your view model. Let's test it using Robolectric 4, JUnit 4, MockK 1.9:

  1. Add @RunWith(RobolectricTestRunner::class) to your test class.
  2. Create an activity controller using Robolectric.buildActivity(FragmentActivity::class.java)
  3. Initialise the activity using setup on the controller, this allows it to be destroyed.
  4. Get the activity with the controller's get method.
  5. Get your view model with the steps described above.
  6. Destroy the activity using destroy on the controller.
  7. 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() {}
}

Unit Testing for ViewModel - MindOrks, We should avoid all android related dependencies in ViewModel to be able to test with pure JUnit test. Let's get into code: I have used Dagger to  Unit Testing for ViewModel. I am going to explain How to write a unit test for ViewModel. We should avoid all android related dependencies in ViewModel to be able to test with pure JUnit test.

I've just created this extension to 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
}

Android Architecture Components: Testing your ViewModel LiveData, Android Architecture Components: Testing your ViewModel LiveData your ViewModel — this will allow us to ensure that our ViewModel classes remain to verify that our observer onChanged() is called when a postValue() method should to how you can write unit tests for your ViewModel methods. The first way in which we can test our ViewModels is by using mockito to verify that our observer onChanged () is called when a postValue () method should have been triggered within our ViewModel.

In kotlin I found that I could override the protected visibility using public and then I can call it from a test.

class MyViewModel: ViewModel() {
    public override fun onCleared() {
        ///...
    }
}

ViewModel Overview, ViewModel lets you manage your UI's data in a lifecycle-aware Overview · Building local unit tests · Building instrumented unit tests The UI controller needs to manage these calls and ensure the When the owner activity is finished, the framework calls the ViewModel objects's onCleared() method so  Unit Testing viewModelScope. Dispatchers.Main uses the Android Looper.getMainLooper() method to run code in the UI thread. That method is available in Instrumented Android tests but not in Unit tests.

Android Jetpack: ViewModel and Testing, GDG Korea Android - Google I/O Extended Android. KimKevin Android Developer / Kakaobank Android Jetpack ViewModel and Testing ViewModel Model class UserViewModel : ViewModel() { val name = MutableLiveData<​String>() } class Think of unit tests as documentation for future developers ! Learn Unit Testing in Android by building a sample application. /** * Called when the "Save" button is clicked. */ public void onSaveClick Another test case to ensure that a proper

When is the viewmodel onCleared called, android fragment viewmodel viewmodel onCleared() Log.d("TEST", "clear") } and the onCleared method is not be called after activity finished. If I want the  Write your first unit tests. Unit tests are supposed to be isolated. Therefore, in each test we only want to test 1 class. We need to either stub or mock the external dependencies. For example, in our SearchPresenterTest (testing SearchPresenter logics), we will mock the Repository and ViewContract here.

Building Unit Tests for ViewModel in TDD style, To achieve that you need to write and maintain unit tests. I will show you simple way of creating unit tests for Android ViewModel implementation with step-by-​step about disposing every call in onCleared method from ViewModel . We shouldn't make verification if venueRepository was called with  For our unit testing I’m going to use JUnit and Mockito. Also, we need the testing library for android architecture components. Before reading this article you have to know that I assume that you are aware about the basics of unit testing on Android, Mockito and RxJava2. I am going to focus in explaining how to test specifically the architecture.

Comments
  • You could public override fun onCleared(), but that exposes the method which is not good, as the method should only be called by the Android system.
  • This depends on ViewModelStore#clear to call ViewModel#onCleared, and therefore that call is somewhat hidden among these lines of code. However, this solution is quite nice due to its brevity, no need for reflection nor Robolectric / Android tests, and the fact that on Android normally it's a ViewModelStore instance that performs the call. Thanks for your answer!