【问题标题】:Android test Fragmen with mock ViewModel using HiltAndroid 使用 Hilt 使用模拟 ViewModel 测试 Fragmen
【发布时间】:2022-01-05 05:42:09
【问题描述】:

我正在使用 Hilt 开发应用程序,一切正常,但是当我尝试在运行低于 Android P 的设备上运行一些 Espresso 测试时,我遇到了问题。

当我尝试模拟(使用 Mockk)ViewModel 以便我可以对我的 Fragment 进行单元测试时,问题就出现了。当 Fragment 尝试实例化 ViewModel 时,我在创建 ViewModel 时收到 NullPointerException。 NPE 被抛出方法setTagIfAbsent。问题是这个方法是包私有的,你可以在ViewModel source code上看到,所以它不能在Android上模拟

我尝试过使用Kotlin All-Open plugin,它有助于模拟 ViewModel 并将其存入公共方法。我尝试使用 mockk 私有存根来存根 setTagIfAbsent,如下所示:

every{
    myViewModelMock["setTagIfAbsent"](any<String>,any())
} answers {secondArg()}

但是当setTagIfAbsent被调用时,真正的方法被调用,抛出NPE,因为ViewModel.mBagOfTags为空,因为类是一个模拟。

其余代码如下:

视图模型:

@OpenForTesting
@HiltViewModel
class MyViewModel @Inject constructor MyViewModel(private val dependency: Dependency): ViewModel(){
    //Rest of the code
}

片段:

@AndroidEntryPoint
class MyFragment: Fragment(){
    private val viewModel: MyViewModel by viewModels()
    //Rest of the code
}

测试类:

@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class MyFragmentTest {

    @Bind
    @MockK
    lateinit var viewModel: MyViewModel

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @Before
    fun prepareTest(){
        MockkAnnotations.init(this)
        hiltRule.inject()
        launchFragmentInHiltContainer<MyFragment>()
    }

    @Test
    fun testThatWillMakeAViewModelInvokation(){
         onView(withId(R.id.button)).perform(click())
         //Assume that button will make the ViewModel be called and created by the delegate
         //When this happens the NPE is thrown
    }
}

方法launchFragmentInHiltContainer来自here (Hilt sample app)

如果你看Mockk Android documentation,据说

有没有人知道我该如何解决这个问题或如何修复测试?

提前致谢。

【问题讨论】:

    标签: android kotlin dagger-hilt mockk


    【解决方案1】:

    您可以在已模拟的 ViewModel 实例上使用反射来模拟 mBagOfTags,而不是模拟 setTagIfAbsent

    setInternalFieldValue(mockedViewModel, "mBagOfTags", HashMap<String, Any>())
    
    fun setInternalFieldValue(target: Any, fieldName: String, value: Any, javaClass: Class<in Any> = target.javaClass) {
        try {
            val field = javaClass.getDeclaredField(fieldName)
            field.isAccessible = true
            field.set(target, value)
        } catch (exception: NoSuchFieldException) {
            val superClass = javaClass.superclass
            if (superClass != null) {
                setInternalFieldValue(target, fieldName, value, superClass)
            } else {
                throw RuntimeException("Field $fieldName is not declared in a hierarchy of this class")
            }
        }
    }
    

    【讨论】:

    • 非常感谢,它可以工作,虽然它很丑,没有更好的方法来解决这个问题吗?
    【解决方案2】:

    如果你使用 mockk(relaxed = true) 这个问题就解决了

    【讨论】:

    • 这就是答案。谢谢!
    猜你喜欢
    • 2021-07-14
    • 2021-04-18
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-27
    相关资源
    最近更新 更多