Вот три реализации, которые работают в теории. Однако последнее решение является лучшим, Храните значения расширения с getStore
и параметры ввода с использованием ParameterResolver
, поскольку оно обеспечивает безопасность жизненного цикла.
Спасибо @ johanneslink , за то, что направили меня в правильном направлении!
Регистрация расширения программы
Стратегия
TLDR - Использовать Регистрация программных расширений .
Эта стратегия работает, как и ожидалось, с TestCoroutineDispatcher
, созданным в MainCoroutineExtension
, и его жизненный цикл управляется с помощью тестовых реализаций жизненного цикла.
Реализация
Test.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.
}
}
Extension.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()
}
}
InjectПараметры, использующие ParameterResolver
Стратегия
TLDR - Используйте ParameterResolver
.
Этот подход реализует ParameterResolver
для ввода TestCoroutineDispatcher
, необходимого для управления жизненным циклом Coroutine в локальном тесте JUnit.
Реализация
Test.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.
}
}
Extension.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
Единственный рефактор, который отличается от Параметры ввода, использующие ParameterResolver
выше, используют getStore
для хранения TestCoroutineDispatcher
. Важно, чтобы context?.root
использовалось во избежание создания нескольких экземпляров введенного значения для одного класса Test.
Это вместо сохранения TestCoroutineDispatcher
в качестве переменной-члена, что может привести к проблемам жизненного цикла, когдапараллельные тесты
Extension.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)
}