Issue
How may multiple values be injected into the test using the ParameterResolver pattern?
It seems that only one return value may be defined.
Currently, getStore
saves an extension value which is injected as a parameter using ParameterResolver
. In this example, it is a TestCoroutineDispatcher
injected to manage the Coroutine lifecycle in a local JUnit test. What about if a second value needs to be injected from the same extension?
Implementation
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 : BeforeAllCallback, AfterAllCallback, BeforeEachCallback,
AfterEachCallback, ParameterResolver {
...
override fun beforeEach(context: ExtensionContext?) {
// Set Coroutine Dispatcher.
Dispatchers.setMain(context?.getStore(STORE_NAMESPACE)
?.get(STORE_KEY, TestCoroutineDispatcher::class.java)!!)
...
}
override fun afterEach(context: ExtensionContext?) {
// Reset Coroutine Dispatcher.
Dispatchers.resetMain()
context?.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?) =
TestCoroutineDispatcher().apply {
extensionContext?.getStore(STORE_NAMESPACE)?.put(STORE_KEY, this)
}
}
Solution
Thank you for the insight @Slaw!
Well, both #supportsParameter and #resolveParameter is invoked for each parameter in the method. So you currently have a test to see if the parameter type is TestCoroutineDispatcher which means you can add a test for the shared ViewModel type. Then resolve the correct parameter based on the type. If you want to share the ViewModel across multiple tests then you can store a reference to it in the parent/root ExtensionContext.Store.
Test.kt
@ExtendWith(LifecycleExtensions::class)
// The TestCoroutineDispatcher is injected here as a parameter.
class FeedLoadContentTests(val testDispatcher: TestCoroutineDispatcher, val contentViewModel: ContentViewModel) {
private fun FeedLoad() = feedLoadTestCases()
@ParameterizedTest
@MethodSource("FeedLoad")
// Injected testDispatcher used here.
fun `Feed Load`(test: FeedLoadContentTest) = testDispatcher.runBlockingTest {
// Some testing done here.
// Injected contentViewModel used here.
}
}
Extension.kt
class LifecycleExtensions : BeforeAllCallback, AfterAllCallback, BeforeEachCallback,
AfterEachCallback, ParameterResolver {
override fun beforeEach(context: ExtensionContext?) {
// Set Coroutine Dispatcher.
Dispatchers.setMain(context?.root
?.getStore(TEST_COROUTINE_DISPATCHER_NAMESPACE)
?.get(TEST_COROUTINE_DISPATCHER_KEY, TestCoroutineDispatcher::class.java)!!)
// Set ViewModel
context?.root
?.getStore(VIEWMODEL_NAMESPACE)
?.get(CONTENT_VIEWMODEL_KEY, ContentViewModel::class.java)!!
// Set LiveData Executor.
ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
override fun afterEach(context: ExtensionContext?) {
// Reset Coroutine Dispatcher.
Dispatchers.resetMain()
context?.root
?.getStore(TEST_COROUTINE_DISPATCHER_NAMESPACE)
?.get(TEST_COROUTINE_DISPATCHER_KEY, TestCoroutineDispatcher::class.java)!!
.cleanupTestCoroutines()
// Clear LiveData Executor
ArchTaskExecutor.getInstance().setDelegate(null)
}
override fun resolveParameter(parameterContext: ParameterContext?,
extensionContext: ExtensionContext?) =
if (parameterContext?.parameter?.type == TestCoroutineDispatcher::class.java)
getTestCoroutineDispatcher(extensionContext).let { dipatcher ->
if (dipatcher == null) saveAndReturnTestCoroutineDispatcher(extensionContext)
else dipatcher
}
else getViewModel(extensionContext).let { viewModel ->
if (viewModel == null) saveAndReturnContentViewModel(extensionContext)
else viewModel
}
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)
}
private fun getViewModel(context: ExtensionContext?) = context?.root
?.getStore(VIEWMODEL_NAMESPACE)
?.get(CONTENT_VIEWMODEL_KEY, ContentViewModel::class.java)
private fun saveAndReturnContentViewModel(extensionContext: ExtensionContext?) =
ContentViewModel().apply {
extensionContext?.root
?.getStore(VIEWMODEL_NAMESPACE)
?.put(CONTENT_VIEWMODEL_KEY, ContentViewModel())
}
}
Answered By - Adam Hurwitz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.