如何在JUnit 5扩展中存储值并如何在参数化测试中进行注入

亚当·赫维兹

总览

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

已观察-无法访问testDispatcherExtension类中创建变量

扩展实施

测试文件


@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.
    }
}

扩展名

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,因为它可以确保生命周期安全。

感谢@johanneslink,指导我朝正确的方向前进!

程序扩展注册

战略

TLDR-使用程序扩展注册

该策略可以与中TestCoroutineDispatcher创建的一样正常工作MainCoroutineExtension,并且其生命周期可以通过测试生命周期实现进行管理。

实作

测试文件

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.
        }
}

扩展名

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

战略

TLDR-使用ParameterResolver

ParameterResolver为了TestCoroutineDispatcher在本地JUnit测试中注入管理Coroutine生命周期所必需的方法,该方法实现了

实作

测试文件

@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.
    }
}

扩展名

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为成员变量,而是在并行运行测试时可能导致生命周期问题。

扩展名

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)
        }

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章