概述
预计 - 创建一个 JUnit 5 Extension
类以管理 TestCoroutineDispatcher
的使用.
观察 - 无法访问 testDispatcher
在 Extension
中创建的变量类(class)。
扩展实现
测试.kt
@ExtendWith(InstantExecutorExtension::class, MainCoroutineExtension::class)
class FeedLoadContentTests {
private val contentViewModel = ContentViewModel()
private fun FeedLoad() = feedLoadTestCases()
@ParameterizedTest
@MethodSource("FeedLoad")
@ExtendWith(MainCoroutineExtension::class)
fun `Feed Load`(test: FeedLoadContentTest) = testDispatcher.runBlockingTest {
// Some testing done here.
}
}
扩展名.kt
class MainCoroutineExtension : BeforeEachCallback, AfterEachCallback {
val testDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
最佳答案
以下是理论上可行的三个实现。但是,最后一个解决方案是最好的,使用 getStore
存储扩展值并使用 ParameterResolver
注入(inject)参数 ,因为它确保了生命周期安全。
感谢 @johanneslink ,为我指引正确的方向!
程序化扩展注册
战略
TLDR - 使用 Programmatic Extension Registration .
此策略与 TestCoroutineDispatcher
一样有效。创建于 MainCoroutineExtension
,以及它的生命周期通过测试生命周期实现进行管理。
执行
测试.kt
class FeedLoadContentTests {
companion object {
@JvmField
@RegisterExtension
val mainCoroutineExtension = MainCoroutineExtension()
}
private val contentViewModel = ContentViewModel()
private fun FeedLoad() = feedLoadTestCases()
@ParameterizedTest
@MethodSource("FeedLoad")
@ExtendWith(MainCoroutineExtension::class)
fun `Feed Load`(test: FeedLoadContentTest) =
mainCoroutineExtension.testDispatcher.runBlockingTest {
// Some testing done here.
}
}
扩展名.kt
class MainCoroutineExtension : BeforeEachCallback, AfterEachCallback {
val testDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
使用
ParameterResolver
注入(inject)参数战略
TLDR - 使用
ParameterResolver
.这种方法实现了
ParameterResolver
为了注入(inject) TestCoroutineDispatcher
需要在本地 JUnit 测试中管理协程生命周期。执行
测试.kt
@ExtendWith(LifecycleExtensions::class)
// The TestCoroutineDispatcher is injected here as a parameter.
class FeedLoadContentTests(val testDispatcher: TestCoroutineDispatcher) {
private val contentViewModel = ContentViewModel()
private fun FeedLoad() = feedLoadTestCases()
@ParameterizedTest
@MethodSource("FeedLoad")
fun `Feed Load`(test: FeedLoadContentTest) = testDispatcher.runBlockingTest {
// Some testing done here.
}
}
扩展名.kt
class LifecycleExtensions : = BeforeEachCallback, AfterEachCallback, ParameterResolver {
val testDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
// Set Coroutine Dispatcher.
Dispatchers.setMain(testDispatcher)
...
}
override fun afterEach(context: ExtensionContext?) {
// Reset Coroutine Dispatcher.
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
...
}
override fun supportsParameter(parameterContext: ParameterContext?,
extensionContext: ExtensionContext?) =
parameterContext?.parameter?.type == TestCoroutineDispatcher::class.java
override fun resolveParameter(parameterContext: ParameterContext?,
extensionContext: ExtensionContext?) =
testDispatcher
}
使用
getStore
存储扩展值并使用 ParameterResolver
注入(inject)参数这里唯一不同于 的重构使用
ParameterResolver
注入(inject)参数以上,使用getStore
存储TestCoroutineDispatcher
.重要的是 context?.root
用于避免为每个 Test 类创建注入(inject)值的多个实例。这不是存储
TestCoroutineDispatcher
作为成员变量,在并行运行测试时可能会导致生命周期问题。扩展名.kt
class LifecycleExtensions : BeforeAllCallback, AfterAllCallback, BeforeEachCallback,
AfterEachCallback, ParameterResolver {
...
override fun beforeEach(context: ExtensionContext?) {
// Set Coroutine Dispatcher.
Dispatchers.setMain(context?.root
?.getStore(STORE_NAMESPACE)
?.get(STORE_KEY, TestCoroutineDispatcher::class.java)!!)
...
}
override fun afterEach(context: ExtensionContext?) {
// Reset Coroutine Dispatcher.
Dispatchers.resetMain()
context?.root
?.getStore(STORE_NAMESPACE)
?.get(STORE_KEY, TestCoroutineDispatcher::class.java)!!.cleanupTestCoroutines()
...
}
override fun supportsParameter(parameterContext: ParameterContext?,
extensionContext: ExtensionContext?) =
parameterContext?.parameter?.type == TestCoroutineDispatcher::class.java
override fun resolveParameter(parameterContext: ParameterContext?,
extensionContext: ExtensionContext?) =
getTestCoroutineDispatcher(extensionContext).let { dipatcher ->
if (dipatcher == null) saveAndReturnTestCoroutineDispatcher(extensionContext)
else dipatcher
}
private fun getTestCoroutineDispatcher(context: ExtensionContext?) = context?.root
?.getStore(TEST_COROUTINE_DISPATCHER_NAMESPACE)
?.get(TEST_COROUTINE_DISPATCHER_KEY, TestCoroutineDispatcher::class.java)
private fun saveAndReturnTestCoroutineDispatcher(extensionContext: ExtensionContext?) =
TestCoroutineDispatcher().apply {
extensionContext?.root
?.getStore(TEST_COROUTINE_DISPATCHER_NAMESPACE)
?.put(TEST_COROUTINE_DISPATCHER_KEY, this)
}
关于android - 如何在 JUnit 5 扩展中存储值并在参数化测试中注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58586207/