Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/visual-studio/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Unit testing 模拟改装暂停功能无限响应_Unit Testing_Mockito_Kotlin Coroutines_Android Viewmodel - Fatal编程技术网

Unit testing 模拟改装暂停功能无限响应

Unit testing 模拟改装暂停功能无限响应,unit-testing,mockito,kotlin-coroutines,android-viewmodel,Unit Testing,Mockito,Kotlin Coroutines,Android Viewmodel,我想测试服务器不返回响应时的情况,我们触发下一个网络调用(例如搜索查询) 所以我们基本上有一个方法,内部视图模型和改造方法 interface RetrofitApi { @GET("Some Url") suspend fun getVeryImportantStuff(): String } class TestViewModel(private val api: RetrofitApi) : ViewModel() { private var askJob:

我想测试服务器不返回响应时的情况,我们触发下一个网络调用(例如搜索查询)

所以我们基本上有一个方法,内部视图模型和改造方法

  interface RetrofitApi {
    @GET("Some Url")
    suspend fun getVeryImportantStuff(): String
}

class TestViewModel(private val api: RetrofitApi) : ViewModel() {

    private var askJob: Job? = null
    fun load(query: String) {
        askJob?.cancel()
        askJob = viewModelScope.launch {
            val response = api.getVeryImportantStuff()

            //DO SOMETHING WITH RESPONSE

        }
    }
}
当新的查询被询问,而旧的查询没有返回时,我想测试这个用例。 对于响应返回的情况,测试很容易

@Test
    fun testReturnResponse() {
        runBlockingTest {
            //given
            val mockApi:RetrofitApi = mock()
            val viewModel = TestViewModel(mockApi)
            val response = "response from api"

            val query = "fancy query"
            whenever(mockApi.getVeryImportantStuff()).thenReturn(response)

            //when
            viewModel.load(query)


            //then
            //verify what happens
        }
    }
但我不知道如何模拟并没有返回的挂起函数,并在新请求被触发时进行测试

@Test
    fun test2Loads() {
        runBlockingTest {
            //given
            val mockApi:RetrofitApi = mock()
            val viewModel = TestViewModel(mockApi)
            val response = "response from api"
            val secondResponse = "response from api2"

            val query = "fancy query"
            whenever(mockApi.getVeryImportantStuff())
                .thenReturn(/* Here return some fancy stuff that is suspend* or something like onBlocking{} stub but not  blocking but dalayed forever/)
                .thenReturn(secondResponse)

            //when
            viewModel.load(query)
            viewModel.load(query)


            //then
            //verify that first response did not happens , and only second one triggered all the stuff
        }
    }
        class TestViewModelTest {

        @get:Rule
        val coroutineScope = MainCoroutineScopeRule()
        @get:Rule
        val instantTaskExecutorRule = InstantTaskExecutorRule()


        lateinit var retrofit: RetrofitApi

        lateinit var utils: CoroutineUtils

        val tottalyDifferentDispatcher = TestCoroutineDispatcher()

        lateinit var viewModel: TestViewModel
        @Before
        fun setup() {
            retrofit = mock()
            utils = mock()
            viewModel = TestViewModel(retrofit, utils)
        }


        @UseExperimental(ExperimentalCoroutinesApi::class)
        @Test
        fun test2Loads() {
            runBlockingTest {
                //given
                val response = "response from api"
                val response2 = "response from api2"
                val query = "fancy query"
                val query2 = "fancy query2"

                whenever(utils.io)
                    .thenReturn(tottalyDifferentDispatcher)

                val mutableListOfStrings = mutableListOf<String>()

                whenever(retrofit.getVeryImportantStuff(query)).thenReturn(response)
                whenever(retrofit.getVeryImportantStuff(query2)).thenReturn(response2)

                //when

                viewModel.testStream.observeForever {
                    mutableListOfStrings.add(it)
                }
                tottalyDifferentDispatcher.pauseDispatcher()
                viewModel.load(query)
                viewModel.load(query2)

                tottalyDifferentDispatcher.resumeDispatcher()

                //then
                mutableListOfStrings shouldHaveSize 1
                mutableListOfStrings[0] shouldBe response2
                verify(retrofit, times(1)).getVeryImportantStuff(query2)
            }
        }
    }
    interface RetrofitWrapper {
     suspend fun getVeryImportantStuff(): suspend (String)->String
    }
    class TestViewModel(private val api: RetrofitWrapper,
                        private val utils: CoroutineUtils) : ViewModel() {

        private val text = MutableLiveData<String>()
        val testStream: LiveData<String> = text
        private var askJob: Job? = null
        fun load(query: String) {
            askJob?.cancel()
            askJob = viewModelScope.launch {
                val veryImportantStuff = api.getVeryImportantStuff()
                val response = withContext(utils.io) {
                    veryImportantStuff(query)
                }
                text.postValue(response)
            }
        }
    }
有什么想法吗

编辑:我不是真的喜欢mockito,任何模拟库都会很好:) 当做
Wojtek看起来像是要在服务器无法访问、超时或类似情况下测试场景

在这种情况下,在执行模拟时,您可以说在第一次尝试时它返回对象,然后在第二次执行时抛出异常,如
java.net.ConnectException:Connection timed out

                whenever(mockApi.getVeryImportantStuff())
                .thenReturn(someObjet)
                .thenThrow(ConnectException("timed out"))
这应该是可行的,但您必须在ViewModel中执行try/catch块,这并不理想。我建议您添加额外的抽象

您可以使用
存储库
用例
或任何您喜欢的模式/名称将网络调用移动到那里。然后引入
sealed class Result
来封装行为,并使您的
ViewModel
更具可读性

class TestViewModel(val repo: Repo): ViewModel() {
    private var askJob: Job? = null

    fun load(query: String) {
        askJob?.cancel()
        askJob = viewModelScope.launch {
            when (repo.getStuff()) {
                is Result.Success -> TODO()
                is Result.Failure -> TODO()
            }
        }
    }
}

class Repo(private val api: Api) {
    suspend fun getStuff() : Result {
        return try {
            Result.Success(api.getVeryImportantStuff())
        } catch (e: java.lang.Exception) {
            Result.Failure(e)
        }
    }
}

sealed class Result {
    data class Success<out T: Any>(val data: T) : Result()
    data class Failure(val error: Throwable) : Result()
}

interface Api {
    suspend fun getVeryImportantStuff() : String
}
类TestViewModel(val repo:repo):ViewModel(){
私有变量askJob:作业?=null
乐趣加载(查询:字符串){
askJob?.cancel()
askJob=viewModelScope.launch{
何时(repo.getStuff()){
是Result.Success->TODO()
是Result.Failure->TODO()
}
}
}
}
类别回购(专用val api:api){
suspend fun getStuff():结果{
回击{
Result.Success(api.getveryiimportantstuff())
}catch(e:java.lang.Exception){
结果.失败(e)
}
}
}
密封类结果{
数据类成功(val数据:T):结果()
数据类失败(val错误:可丢弃):结果()
}
接口Api{
suspend fun getVeryImportantStuff():字符串
}
通过这种抽象级别,您的
ViewModelTest
只检查两种情况下发生的情况


希望对你有帮助

我想出了解决这个问题的办法,但与我一开始的想法略有不同

        interface CoroutineUtils {
            val io: CoroutineContext
        }

        interface RetrofitApi {
            @GET("Some Url")
            suspend fun getVeryImportantStuff(query: String): String
        }

        class TestViewModel(private val api: RetrofitApi,
                            private val utils: CoroutineUtils) : ViewModel() {
        private val text = MutableLiveData<String>()
        val testStream: LiveData<String> = text
        private var askJob: Job? = null
        fun load(query: String) {
            askJob?.cancel()
            askJob = viewModelScope.launch {
                val response = withContext(utils.io) { api.getVeryImportantStuff(query) }
                text.postValue(response)
            }
        }
    }
并对其进行测试

    @Test
    fun test2Loads() {
        runBlockingTest {
            //given
            val response = "response from api"
            val response2 = "response from api2"
            val query = "fancy query"
            val query2 = "fancy query2"

            whenever(utils.io)
                .thenReturn(tottalyDifferentDispatcher)

            val mutableListOfStrings = mutableListOf<String>()

            whenever(retrofit.getVeryImportantStuff())
                .thenReturn(suspendCoroutine {
                    it.resume { response }
                })
            whenever(retrofit.getVeryImportantStuff()).thenReturn(suspendCoroutine {
                it.resume { response2 }
            })

            //when

            viewModel.testStream.observeForever {
                mutableListOfStrings.add(it)
            }
            tottalyDifferentDispatcher.pauseDispatcher()
            viewModel.load(query)
            viewModel.load(query2)

            tottalyDifferentDispatcher.resumeDispatcher()

            //then
            mutableListOfStrings shouldHaveSize 1
            mutableListOfStrings[0] shouldBe response2
            verify(retrofit, times(2)).getVeryImportantStuff()
        }
    }
@测试
有趣的test2Loads(){
运行阻塞测试{
//给定
val response=“来自api的响应”
val response2=“来自api2的响应”
val query=“花式查询”
val query2=“花式query2”
无论何时(utils.io)
.然后返回(总计不同的喷射器)
val mutableListOfStrings=mutableListOf()
无论何时(改装.getVeryImportantStuff())
.thenReturn(suspendCoroutine{
it.resume{response}
})
无论何时(改装.getVeryImportantStuff())。然后返回(suspendCoroutine{
it.resume{response2}
})
//什么时候
viewModel.testStream.observeForever{
mutableListOfStrings.add(它)
}
TotalyDifferentintDispatcher.pauseDispatcher()
加载(查询)
viewModel.load(查询2)
TotalyDifferentintDispatcher.resumeDispatcher()
//然后
可变字符串列表应保存大小1
mutableListOfStrings[0]应该是response2
验证(改装,次数(2)).getVeryImportantStuff()
}
}

但在我看来,这是一个有点太多的干扰,在代码中只能测试。但也许我错了:P

不太可能,因为问题是恢复暂停,而不是例外。问题在于,当第一个响应并没有返回,而第二个请求被触发时,测试用例就开始了。将改型传递到ViewModel只是为了简单起见。但是谢谢你的努力,我想你不明白协同程序是如何工作的,或者我们无法理解彼此的问题是什么。协同路由挂起函数是异步执行的,所以基本上调用查询方法两次将触发两个不同的作业(在第一个作业未取消的情况下)。目前的目标是测试第一个被取消。在这个简单的场景中,但是还有很多地方这样的嘲弄是有用的是的,看起来我们彼此不理解。在您的场景中使用runBlockingTest将不起作用,因为每个挂起的值都将立即返回,所有协同路由要么必须完成,要么被取消。我认为您的测试场景在不支持线程的JUnit框架下是不可行的!我在考虑类似的东西,但我总是使用
val io:CoroutineDispatcher
。老实说,这看起来有点过分了,但现在我们知道这是可行的。是的,但是如果你想出了更好的主意,不要犹豫,告诉大家:D我不喜欢这个