使用Mockito和Kotlin监视具有暂停功能的类

问题描述

我的源代码

open class RestaurantListviewmodel @Inject constructor(
    private val restaurantApi: RestaurantApi
): viewmodel() {

    private val _listofRestaurantsHolder = mutablelivedata<List<Restaurant>>()

    private val _selectedRestaurant = mutablelivedata<Restaurant>()

    /**
     * Fetches all restaurants from a service call.
     */
    fun fetchAllRestaurants()
    {
        viewmodelScope.launch {
           fetchRestaurantsAsync()
            _selectedRestaurant.value = null
        }
    }

    /**
     * Sets either the success response to the [_selectedRestaurant] live data or `null` in case of
     * error response
     */
    internal suspend fun fetchRestaurantsAsync() {
        withContext(dispatchers.IO) {
            restaurantApi.fetchRestaurantList().enqueue(object: Callback<List<Restaurant>> {
                override fun onFailure(call: Call<List<Restaurant>>,t: Throwable) {
                    _listofRestaurantsHolder.value = null
                }

                override fun onResponse(
                    call: Call<List<Restaurant>>,response: Response<List<Restaurant>>
                ) {
                    _listofRestaurantsHolder.value = response.body()
                }

            })
        }
    }
}

测试

@ExperimentalCoroutinesApi
class RestaurantListviewmodelTest {

    @get:Rule
    val coroutinescope = MainCoroutinescopeRule()

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    public val rule:  MockitoRule = MockitoJUnit.rule()

    lateinit var restaurantListviewmodel: RestaurantListviewmodel

    @Mock
    private lateinit var restaurantApi: RestaurantApi

    @Before
    fun setUp() {
        restaurantListviewmodel = spy(RestaurantListviewmodel((restaurantApi)))
    }

    @Test
    fun `test fetch all restaurants`() = runBlockingTest {
        donothing().whenever(restaurantListviewmodel).fetchRestaurantsAsync()
        restaurantListviewmodel.fetchAllRestaurants()
        verify(restaurantListviewmodel).fetchRestaurantsAsync()
    }
}

自定义协程范围:https://github.com/googlecodelabs/kotlin-coroutines/blob/master/coroutines-codelab/start/src/test/java/com/example/android/kotlincoroutines/main/utils/MainCoroutineScopeRule.kt

错误

java.lang.IllegalStateException: This job has not completed yet

    at kotlinx.coroutines.JobSupport.getCompletionExceptionorNull(JobSupport.kt:1189)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:53)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
    at com.myproject.restaurant.viewmodels.RestaurantListviewmodelTest.test fetch all restaurants(RestaurantListviewmodelTest.kt:43)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.mockito.internal.junit.JUnitRule$1.evaluateSafely(JUnitRule.java:52)
    at org.mockito.internal.junit.JUnitRule$1.evaluate(JUnitRule.java:43)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runchild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runchild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runchildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

disconnected from the target VM,address: '127.0.0.1:56527',transport: 'socket'

进程结束,退出代码为255

期望的行为

我希望能够在Kotlin中测试这两种方法。我会用Java完成以下操作。

 donothing().whenever(restaurantListviewmodel).fetchRestaurantsAsync()

但是当我尝试用这种方式模拟一个暂停函数时,它会调用real方法,这不是我想要的。

当我如下更改测试方法

 @Test
        fun `test fetch all restaurants`() = runBlocking {
            donothing().whenever(restaurantListviewmodel).fetchRestaurantsAsync()
            restaurantListviewmodel.fetchAllRestaurants()
            verify(restaurantListviewmodel).fetchRestaurantsAsync()
        }

调用real方法并引发以下异常

java.lang.NullPointerException
    at com.myproject.restaurant.RestaurantListviewmodel$fetchRestaurantsAsync$2.invokeSuspend(RestaurantListviewmodel.kt:41)
    at |b|b|b(Coroutine boundary.|b(|b)
    at com.krishnanand.doordash.viewmodels.RestaurantListviewmodelTest$test fetch all restaurants$1.invokeSuspend(RestaurantListviewmodelTest.kt:44)

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)