Unit testing coroutines on UI thread


#1

I’m using coroutines to do an asynchronous call on pull to refresh like so:

class DataFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {

    // other functions here

    override fun onRefresh() {
        loadDataAsync()
    }

    private fun loadDataAsync() = async(UI) {
        swipeRefreshLayout?.isRefreshing = true
        progressLayout?.showContent()

        val data = async(CommonPool) {
            service?.getData() // suspending function
        }.await()

        when {
            data == null -> showError()
            data.isEmpty() -> progressLayout?.showEmpty(null, parentActivity?.getString(R.string.no_data), null)
            else -> {
                dataAdapter?.updateData(data)
                dataAdapter?.notifyDataSetChanged()
                progressLayout?.showContent()
            }
        }

        swipeRefreshLayout?.isRefreshing = false
    }
}

Everything here works fine when I actually put it on a device. My error, empty, and data states are all handled well and the performance is good. However, I’m also trying to unit test it with Spek. My Spek test looks like this:

@RunWith(JUnitPlatform::class)
class DataFragmentTest : Spek({

    describe("The DataFragment") {

        var uut: DataFragment? = null
        
        beforeEachTest {
            uut = DataFragment()
        }

        // test other functions
        
        describe("when onRefresh") {
            beforeEachTest {
                uut?.swipeRefreshLayout = mock()
                uut?.onRefresh()
            }

            it("sets swipeRefreshLayout.isRefreshing to true") {
                verify(uut?.swipeRefreshLayout)?.isRefreshing = true // says no interaction with mock
            }
        }
    }           
}

The test is failing because it says that there was no interaction with the uut?.swipeRefreshLayout mock. After some experimenting, it seems this is because I’m using the UI context via async(UI). If I make it just be a regular async, I can get the test to pass but then the app crashes because I’m modifying views outside of the UI thread.

Any ideas why this might be occurring? Also, if anyone has any better suggestions for doing this which will make it more testable, I’m all ears.

Thanks.

EDIT: Forgot to mention that I also tried wrapping the verify and the uut?.onRefresh() in a runBlocking, but I still had no success.


#2

You can’t use android stuf like a Handler and Looper on JVM.
You must run it like instrumentation test or u can apply some di logic to your fragment and for test use different context