androidrobolectricandroid-livedata

Testing livedata with Robolectric


I have following test that checks if activity correctly gets data from repository through viewmodel.

@Config(application = TestApplication::class)
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
class BusinessTests {
    private lateinit var viewModel: BusinessCollectionViewModel
    private lateinit var activity: BusinessCollectionVerticalActivity
    private lateinit var observer: Observer<Triple<NetworkState, PagedList<Edge<Business>>, TimeTracking?>>

    @Before
    fun setUp() {
        observer = mock()
    }

    @Test
    fun givenBusinessMock_whenVerticalCollection_thenBusinessVerticalWith2Items() {

        val activityScenario = ActivityScenario.launch(BusinessCollectionVerticalActivity::class.java)
        activityScenario.onActivity {
            activity = it
        }

        viewModel = ViewModelProviders.of(activity)[BusinessCollectionViewModel::class.java]

        viewModel.data.observeForever(observer)
        assert(viewModel.data.value?.second?.size == 2)
    }
}

Problem is that the test always fails, but in debug it passes correctly, but when I debug it with false condition in assert, following exception pops up.

java.lang.Exception: Main looper has queued unexecuted runnables. This might be the cause of the test failure. You might need a shadowOf(getMainLooper()).idle() call.

It's really strange behaviour and I don't know what to do. And of course I tried to add shadowOf(getMainLooper()).idle() before observe.

I'm using latest robolectric 4.3, could it be a bug?


Solution

  • The problem is that the thread that runs the UI test is finishing without any errors while the assert is running in a different thread.

    You can change your test method to hold the thread until it's release by the thread that runs the observeOnce:

    @Test
    fun givenBusinessMock_whenVerticalCollection_thenBusinessVerticalWith2Items() {
    
        val syncObject = Object()
    
        val activityScenario = ActivityScenario.launch(BusinessCollectionVerticalActivity::class.java)
        activityScenario.onActivity {
            activity = it
        }
    
        viewModel = ViewModelProviders.of(activity)[BusinessCollectionViewModel::class.java]
    
        viewModel.data.observeOnce {
            assert(it.second.size == 2)
    
            synchronized (syncObject) {
                syncObject.notify()
            }
        }
    
        synchronized (syncObject) {
            syncObject.wait()
        }
    }
    

    If you want a better approach, you can observeForever in your data and check the value:

    var observer: Observer<?> // replace ? by your type
    viewModel.data.observeForever(observer)
    assert(viewModel.data.value.second.size == 2)
    

    More info in this article.