对使用 kotlin 协程的改造调用进行单元测试
Unit testing a retrofit call that uses kotlin coroutines
我正在使用 kotlin coroutines and kotlin retrofit coroutines 在我当前正在处理的项目中执行网络请求。但是我无法弄清楚如何为逻辑通过进行单元测试。
这是我的代码:
class WorklistInteractor @Inject
constructor(private val worklistRepository: WorklistRepository,
private val preferenceManager: PreferenceManager)
: NetworkInteractor, WorklistDialogContract.Interactor {
private var job = Job()
override fun getWorklist(listener: OnWorklistResultListener) {
job = launch(UI) {
val result = async {
worklistRepository.getWorklist(
ip = preferenceManager.worklistIp,
port = preferenceManager.worklistPort).awaitResult()
}.await()
when (result) {
//Successful HTTP result
is Result.Ok -> listener.onWorklistResult(result.value)
// Any HTTP error
is Result.Error -> {
Timber.e(result.exception, "HTTP error with code %s}", result.exception.code())
when(result.exception.code()) {
401 -> listener.onInvalidCredentialsFailure()
500 -> listener.internalServerError()
503 -> listener.noServerResponseFailure()
else -> listener.onError(result.exception.cause.toString())
}
}
// Exception while request invocation
is Result.Exception -> {
Timber.e(result.exception.cause, "Exception with cause %s", result.exception.cause.toString())
when(result.exception) {
is ConnectException -> listener.connectionRefused()
is SocketTimeoutException -> listener.failedToConnectToHost()
else -> listener.onError(result.exception.cause.toString())
}
}
}
}
}
override fun cancel() {
job.cancel()
}
}
这是我的单元测试之一:
@Test
fun `when worklistquery returns result, pass result back through listeners onWorklistResult`()
= runBlocking {
whenever(mWorklistRepositoryMock.getWorklist(anyString(), anyInt(), anyString()))
.thenReturn(Calls.response(expectedWorklistResult))
mInteractor.getWorklist(mOnWorklistResultListenerMock)
verify(mOnWorklistResultListenerMock).onWorklistResult(expectedWorklistResult)
verifyNoMoreInteractions(mOnWorklistResultListenerMock)
}
我在 运行 时不断收到以下消息:
Wanted but not invoked:
onWorklistResultListener.onWorklistResult(
);
-> at com.example.dialogs.worklistdialog.WorklistInteractorTest$when worklistquery returns result, pass result back through listeners OnWorklistResult().doResume(WorklistInteractorTest.kt:58)
Actually, there were zero interactions with this mock.
您找到了解决方案,但也许对于其他人,尤其是初学者,解释您一开始遇到错误的原因以及为什么 runBlocking
有帮助可能会有所帮助。问题是,当 运行ning 单元测试时,期望这些测试是同步的,也就是说,如果稍后在某个单独的线程上执行任何代码,测试 运行ner 将永远不知道这一点,因为对于测试 运行ner,当测试 运行ner 的线程上的方法调用完成时,测试就完成了。
因此无法在主线程上测试未 运行ning 的代码。事实上,尝试这样做甚至是一种不好的做法。由于无法保证另一个线程上的代码 运行ning 何时完成(如果有的话),我们不知道主线程当时将处于什么状态(主线程甚至可能不存在了) ,与单元测试 运行ner 的情况一样)。
即使以某种方式可以测试在其他线程中执行的代码,每个测试 运行 也不可避免地会有所不同(由于上述段落)并且测试可能会在每个 [=27] 上产生不同的结果=].这直接违背了测试可靠并在每个 运行.
上产生相同结果的意识形态。
因此在测试时,所有被测试的代码都需要在主线程上 运行 并且应该确保永远不要尝试在测试中调用任何异步代码。
runBlocking
协程,不出所料 运行 在它启动的线程上以阻塞方式 运行 中的代码(与 launch
相反,这将导致代码运行 异步)。
有一点需要注意,协程不是线程,但如果将上面文本中的 thread
一词替换为 asynchronous coroutine
,该文本将不会失去任何意义。我所说的既适用于您使用传统线程的情况,也适用于您使用协程的情况。
必须对实现进行相当大的更改。事实证明,当不从 Android Activity/Fragment.
中使用时,从常规函数中使用 launch
coroutine-builder 并不是那么简单
我没有使用常规函数,而是将 getWorklist()
函数更改为挂起函数并使用了 withContext
协程构建器。这是新的实现:
override suspend fun getWorklist(listener: OnWorklistResultListener) {
withContext(CommonPool) {
Timber.i("Loading worklist")
val result = worklistRepository.getWorklist(
ip = preferenceManager.worklistIp,
port = preferenceManager.worklistPort,
aeTitle = preferenceManager.worklistAeTitle)
.awaitResult()
when (result) {
//Successful HTTP result
... left out for brevity (it's the same as before) ...
现在所有测试都通过了。
我正在使用 kotlin coroutines and kotlin retrofit coroutines 在我当前正在处理的项目中执行网络请求。但是我无法弄清楚如何为逻辑通过进行单元测试。
这是我的代码:
class WorklistInteractor @Inject
constructor(private val worklistRepository: WorklistRepository,
private val preferenceManager: PreferenceManager)
: NetworkInteractor, WorklistDialogContract.Interactor {
private var job = Job()
override fun getWorklist(listener: OnWorklistResultListener) {
job = launch(UI) {
val result = async {
worklistRepository.getWorklist(
ip = preferenceManager.worklistIp,
port = preferenceManager.worklistPort).awaitResult()
}.await()
when (result) {
//Successful HTTP result
is Result.Ok -> listener.onWorklistResult(result.value)
// Any HTTP error
is Result.Error -> {
Timber.e(result.exception, "HTTP error with code %s}", result.exception.code())
when(result.exception.code()) {
401 -> listener.onInvalidCredentialsFailure()
500 -> listener.internalServerError()
503 -> listener.noServerResponseFailure()
else -> listener.onError(result.exception.cause.toString())
}
}
// Exception while request invocation
is Result.Exception -> {
Timber.e(result.exception.cause, "Exception with cause %s", result.exception.cause.toString())
when(result.exception) {
is ConnectException -> listener.connectionRefused()
is SocketTimeoutException -> listener.failedToConnectToHost()
else -> listener.onError(result.exception.cause.toString())
}
}
}
}
}
override fun cancel() {
job.cancel()
}
}
这是我的单元测试之一:
@Test
fun `when worklistquery returns result, pass result back through listeners onWorklistResult`()
= runBlocking {
whenever(mWorklistRepositoryMock.getWorklist(anyString(), anyInt(), anyString()))
.thenReturn(Calls.response(expectedWorklistResult))
mInteractor.getWorklist(mOnWorklistResultListenerMock)
verify(mOnWorklistResultListenerMock).onWorklistResult(expectedWorklistResult)
verifyNoMoreInteractions(mOnWorklistResultListenerMock)
}
我在 运行 时不断收到以下消息:
Wanted but not invoked: onWorklistResultListener.onWorklistResult( ); -> at com.example.dialogs.worklistdialog.WorklistInteractorTest$when worklistquery returns result, pass result back through listeners OnWorklistResult().doResume(WorklistInteractorTest.kt:58)
Actually, there were zero interactions with this mock.
您找到了解决方案,但也许对于其他人,尤其是初学者,解释您一开始遇到错误的原因以及为什么 runBlocking
有帮助可能会有所帮助。问题是,当 运行ning 单元测试时,期望这些测试是同步的,也就是说,如果稍后在某个单独的线程上执行任何代码,测试 运行ner 将永远不知道这一点,因为对于测试 运行ner,当测试 运行ner 的线程上的方法调用完成时,测试就完成了。
因此无法在主线程上测试未 运行ning 的代码。事实上,尝试这样做甚至是一种不好的做法。由于无法保证另一个线程上的代码 运行ning 何时完成(如果有的话),我们不知道主线程当时将处于什么状态(主线程甚至可能不存在了) ,与单元测试 运行ner 的情况一样)。
即使以某种方式可以测试在其他线程中执行的代码,每个测试 运行 也不可避免地会有所不同(由于上述段落)并且测试可能会在每个 [=27] 上产生不同的结果=].这直接违背了测试可靠并在每个 运行.
上产生相同结果的意识形态。因此在测试时,所有被测试的代码都需要在主线程上 运行 并且应该确保永远不要尝试在测试中调用任何异步代码。
runBlocking
协程,不出所料 运行 在它启动的线程上以阻塞方式 运行 中的代码(与 launch
相反,这将导致代码运行 异步)。
有一点需要注意,协程不是线程,但如果将上面文本中的 thread
一词替换为 asynchronous coroutine
,该文本将不会失去任何意义。我所说的既适用于您使用传统线程的情况,也适用于您使用协程的情况。
必须对实现进行相当大的更改。事实证明,当不从 Android Activity/Fragment.
中使用时,从常规函数中使用launch
coroutine-builder 并不是那么简单
我没有使用常规函数,而是将 getWorklist()
函数更改为挂起函数并使用了 withContext
协程构建器。这是新的实现:
override suspend fun getWorklist(listener: OnWorklistResultListener) {
withContext(CommonPool) {
Timber.i("Loading worklist")
val result = worklistRepository.getWorklist(
ip = preferenceManager.worklistIp,
port = preferenceManager.worklistPort,
aeTitle = preferenceManager.worklistAeTitle)
.awaitResult()
when (result) {
//Successful HTTP result
... left out for brevity (it's the same as before) ...
现在所有测试都通过了。