如何制作 MockWebServer +改造 +协程在同一个调度程序中运行

发布于 2025-01-14 07:54:04 字数 2093 浏览 4 评论 0原文

我在 Android 单元测试中尝试使用 MockWebServer + Retrofit + Coroutines 失败了。在调试过程中,我发现OkHttp运行在不同的线程上,这就是为什么我的测试总是失败的原因。

这是我设置测试调度程序的规则:

class MainCoroutineRule(
    private val scheduler: TestCoroutineScheduler = TestCoroutineScheduler(),
    val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(scheduler)
) : TestWatcher() {

    val testScope = TestScope(testDispatcher)

    override fun starting(description: Description) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description) {
        super.finished(description)
        Dispatchers.resetMain()
    }
}

这是我的 ViewModel:

@HiltViewModel
class TestViewModel @Inject constructor(
    private val repository: TestRepository,
    @MainDispatcher private val dispatcher: CoroutineDispatcher
) : ViewModel() {

    val hasData = MutableLiveData(false)

    fun fetchSomething() {
        viewModelScope.launch(dispatcher) {

            when (repository.getSomething()) {
                is Success -> {
                    hasData.value = true
                }
                else -> {
                    hasData.value = false
                }
            }
        }
    }
}

最后是测试:

class TestViewModelTest : MockServerSuite() {
    @get:Rule
    val taskExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    val mainTestRule = MainCoroutineRule()

    @Test
    fun `my test`() = mainTestRule.testScope.runTest {
        // setting up MockWebServer

        val viewModel = TestViewModel(
            repository = TestRepository(
                api = testApi(server),
            ),
            dispatcher = mainTestRule.testDispatcher
        )

        viewModel.fetchSomething()
        assertThat(viewModel.hasData.value).isTrue
    }
}

由于 Retrofit 是主安全的,所以我不知道如何使其在我的 testDispatcher 上运行。我错过了什么吗?

正如 Petrus 提到的,我已经使用了 getOrAwaitValue,它适用于简单的场景。 在大多数用例中,我们的请求在收到一些数据后会触发其他请求。 通常,我收到 getOrAwaitValue 的中间值,而不是我期望的最终 LiveData。

I'm unsuccessfully trying to use MockWebServer + Retrofit + Coroutines on my Android unit tests. During debugging, I found out that OkHttp is running on a different thread, which is why my test always fails.

This is my rule to set the test dispatcher:

class MainCoroutineRule(
    private val scheduler: TestCoroutineScheduler = TestCoroutineScheduler(),
    val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(scheduler)
) : TestWatcher() {

    val testScope = TestScope(testDispatcher)

    override fun starting(description: Description) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description) {
        super.finished(description)
        Dispatchers.resetMain()
    }
}

this is my ViewModel:

@HiltViewModel
class TestViewModel @Inject constructor(
    private val repository: TestRepository,
    @MainDispatcher private val dispatcher: CoroutineDispatcher
) : ViewModel() {

    val hasData = MutableLiveData(false)

    fun fetchSomething() {
        viewModelScope.launch(dispatcher) {

            when (repository.getSomething()) {
                is Success -> {
                    hasData.value = true
                }
                else -> {
                    hasData.value = false
                }
            }
        }
    }
}

And finally the test:

class TestViewModelTest : MockServerSuite() {
    @get:Rule
    val taskExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    val mainTestRule = MainCoroutineRule()

    @Test
    fun `my test`() = mainTestRule.testScope.runTest {
        // setting up MockWebServer

        val viewModel = TestViewModel(
            repository = TestRepository(
                api = testApi(server),
            ),
            dispatcher = mainTestRule.testDispatcher
        )

        viewModel.fetchSomething()
        assertThat(viewModel.hasData.value).isTrue
    }
}

Since Retrofit is main-safe, I have no idea how to make it run on my testDispatcher. Am I missing something?

As Petrus mentioned, I already use getOrAwaitValue, which works for simple scenarios.
Our requests fire other requests after receiving some data in most of our use cases.
Usually, I receive intermediate values of getOrAwaitValue and not the final LiveData I was expecting.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

沫尐诺 2025-01-21 07:54:04

尝试使用 getOrAwaitValue :)

class TestViewModelTest {
    @Test
    fun `my test`() = mainTestRule.testScope.runTest {
        // setting up MockWebServer

        val viewModel = TestViewModel(
            repository = TestRepository(
                api = testApi(server),
            ),
            dispatcher = mainTestRule.testDispatcher
        )

        viewModel.fetchSomething()
        assertThat(viewModel.hasData.getOrAwaitValue()).isTrue
    }
}

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            [email protected](this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

Try to use getOrAwaitValue :)

class TestViewModelTest {
    @Test
    fun `my test`() = mainTestRule.testScope.runTest {
        // setting up MockWebServer

        val viewModel = TestViewModel(
            repository = TestRepository(
                api = testApi(server),
            ),
            dispatcher = mainTestRule.testDispatcher
        )

        viewModel.fetchSomething()
        assertThat(viewModel.hasData.getOrAwaitValue()).isTrue
    }
}

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            [email protected](this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文