sun*_*ns9 37 android unit-testing kotlin mockk kotlin-coroutines
请在下面找到一个使用协程替换回调的函数:
override suspend fun signUp(authentication: Authentication): AuthenticationError {
return suspendCancellableCoroutine {
auth.createUserWithEmailAndPassword(authentication.email, authentication.password)
.addOnCompleteListener(activityLifeCycleService.getActivity()) { task ->
if (task.isSuccessful) {
it.resume(AuthenticationError.SignUpSuccess)
} else {
Log.w(this.javaClass.name, "createUserWithEmail:failure", task.exception)
it.resume(AuthenticationError.SignUpFail)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我想对这个函数进行单元测试。我正在使用 Mockk :
@Test
fun `signup() must be delegated to createUserWithEmailAndPassword()`() = runBlockingTest {
val listener = slot<OnCompleteListener<AuthResult>>()
val authentication = mockk<Authentication> {
every { email } returns "email"
every { password } returns "pswd"
}
val task = mockk<Task<AuthResult>> {
every { isSuccessful } returns true
}
every { auth.createUserWithEmailAndPassword("email", "pswd") } returns
mockk {
every { addOnCompleteListener(activity, capture(listener)) } returns mockk()
}
service.signUp(authentication)
listener.captured.onComplete(task)
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,由于以下异常,此测试失败: java.lang.IllegalStateException: This job has not completed yet
我试图替换为runBlockingTest
,runBlocking
但测试似乎在无限循环中等待。
有人可以帮我解决这个 UT 吗?
提前致谢
Mik*_*rin 12
如果进行Flow
测试:
flow.collect
直接在里面使用runBlockingTest
。它应该被包裹在launch
TestCoroutineScope
不要忘记在测试结束时取消。它将停止Flow
收集。例子:
class CoroutinesPlayground {
private val job = Job()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(job + testDispatcher)
@Test
fun `play with coroutines here`() = testScope.runBlockingTest {
val flow = MutableSharedFlow<Int>()
launch {
flow.collect { value ->
println("Value: $value")
}
}
launch {
repeat(10) { value ->
flow.emit(value)
delay(1000)
}
job.cancel()
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如在这篇文章中可以看到的:
此异常通常意味着您的测试中的某些协程被安排在测试范围之外(更具体地说是测试调度程序)。
而不是执行此操作:
private val networkContext: CoroutineContext = TestCoroutineDispatcher()
private val sut = Foo(
networkContext,
someInteractor
)
fun `some test`() = runBlockingTest() {
// given
...
// when
sut.foo()
// then
...
}
Run Code Online (Sandbox Code Playgroud)
创建一个通过测试调度程序的测试范围:
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
private val networkContext: CoroutineContext = testDispatcher
private val sut = Foo(
networkContext,
someInteractor
)
Run Code Online (Sandbox Code Playgroud)
然后在测试执行 testScope.runBlockingTest
fun `some test`() = testScope.runBlockingTest {
...
}
Run Code Online (Sandbox Code Playgroud)
另请参阅 Craig Russell 的“使用 TestCoroutineDispatcher 进行单元测试协程挂起函数”
这不是官方解决方案,因此请自行承担使用风险。
这类似于@azizbekian 发布的内容,但不是调用runBlocking
,而是调用launch
。由于这是使用TestCoroutineDispatcher,任何计划立即执行的任务都会立即执行。如果您有多个异步运行的任务,这可能不合适。
它可能并不适合所有情况,但我希望它对简单的情况有所帮助。
您也可以在此处跟进此问题:
如果您知道如何使用现有的runBlockingTest
和来解决这个问题runBlocking
,请善待并与社区分享。
class MyTest {
private val dispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(dispatcher)
@Test
fun myTest {
val apiService = mockk<ApiService>()
val repository = MyRepository(apiService)
testScope.launch {
repository.someSuspendedFunction()
}
verify { apiService.expectedFunctionToBeCalled() }
}
}
Run Code Online (Sandbox Code Playgroud)
根据我的理解,当您在代码块内的代码中使用runBlockingTest { }
与启动的调度程序不同的调度程序时,就会发生此异常runBlockingTest { }
。
因此,为了避免这种情况,您首先必须确保在代码中注入调度程序,而不是在整个应用程序中对其进行硬编码。如果您还没有这样做,则无处可开始,因为您无法将测试调度程序分配给您的测试代码。
然后,在你的 中BaseUnitTest
,你应该有这样的东西:
@get:Rule
val coroutineRule = CoroutineTestRule()
Run Code Online (Sandbox Code Playgroud)
@ExperimentalCoroutinesApi
class CoroutineTestRule(
val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher() {
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.setMain(testDispatcher)
}
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
Run Code Online (Sandbox Code Playgroud)
下一步实际上取决于您如何进行依赖注入。要点是确保您的测试代码coroutineRule.testDispatcher
在注入后正在使用。
最后,runBlockingTest { }
从此 testDispatcher 调用:
@Test
fun `This should pass`() = coroutineRule.testDispatcher.runBlockingTest {
//Your test code where dispatcher is injected
}
Run Code Online (Sandbox Code Playgroud)
此问题有一个未解决的问题:https://github.com/Kotlin/kotlinx.coroutines/issues/1204
解决方案是使用 CoroutineScope 代替 TestCoroutinScope 直到问题解决,您可以通过替换
@Test
fun `signup() must be delegated to createUserWithEmailAndPassword()`() =
runBlockingTest {
Run Code Online (Sandbox Code Playgroud)
和
@Test
fun `signup() must be delegated to createUserWithEmailAndPassword()`() =
runBlocking {
Run Code Online (Sandbox Code Playgroud)
由于协程 API 的频繁更改,这些答案都不适合我的设置。
这特别适用于kotlin-coroutines-test 1.6.0 版本,并作为testImplementation
依赖项添加。
@Test
fun `test my function causes flow emission`() = runTest {
// calling this function will result in my flow emitting a value
viewModel.myPublicFunction("1234")
val job = launch {
// Force my flow to update via collect invocation
viewModel.myMemberFlow.collect()
}
// immediately cancel job
job.cancel()
assertEquals("1234", viewModel.myMemberFlow.value)
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
10170 次 |
最近记录: |