android - 如何在 JUnit 5 扩展中存储值并在参数化测试中注入(inject)

标签 android kotlin junit android-testing junit5

概述

预计 - 创建一个 JUnit 5 Extension类以管理 TestCoroutineDispatcher 的使用.

观察 - 无法访问 testDispatcherExtension 中创建的变量类(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/

相关文章:

使用 Netbeans IDE 进行 Android 开发

java - Android Studio-RatingBar setOnTouchListener无法正常工作

android - Butterknife 7.x 是否适用于 Kotlin M14?

java - @WebMvcTest 失败,出现 java.lang.IllegalStateException : Failed to load ApplicationContext

java - 在我使用 Google Play 服务版本 9.0.83 进行的路线跟踪 Activity 中, map 上没有显示标记

android - Horizo​​ntalScrollView : auto-scroll to end when new Views are added?

kotlin - 如何使用exposed根据传入参数添加多个或过滤条件?

kotlin - Kotlin数据类无法初始化父类的属性

java - 我如何将一个 "deserialize"的HTML表格数据转换成一个二维数组?

java - maven jacoco插件不生成覆盖率报告