首页 > 解决方案 > 如何在使用 Android Hilt 进行仪器测试期间将模拟的 ViewModel 注入 Fragment?

问题描述

我很难在 Android 上进行仪器测试。

目标:在Instrumented 测试ViewModel期间注入一个 mocked 。Fragment

语境:

MyViewModel是使用Hilt Jetpack 集成@ViewModelInject注释构建的,如下所示:

class OverviewViewModel @ViewModelInject constructor(
    private val coroutineScopeProvider: CoroutineScope?,
    private val repository: Repository
): ViewModel() {

    private val coroutineScope = getViewModelScope(coroutineScopeProvider)

    val isLogged = repository.isLogged
    val session = repository.session

    fun logout() {
        coroutineScope.launch {
            repository.logout()
        }
    }
}

fun ViewModel.getViewModelScope(coroutineScope: CoroutineScope?) =
    coroutineScope ?: this.viewModelScope

// Need to do that to be able to test the viewModel
@Module
@InstallIn(ActivityComponent::class)
object CoroutineModel {
    @Provides
    fun provideViewScopeModel(): CoroutineScope? = null
}

我的片段使用ViewModel如下:

@AndroidEntryPoint
class OverviewFragment : Fragment() {

    private val viewModel: OverviewViewModel by viewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = DataBindingUtil.inflate<FragmentOverviewBinding>(inflater,
            R.layout.fragment_overview,container,false)

        binding.viewModel = viewModel

        binding.lifecycleOwner = viewLifecycleOwner

        binding.loginButton.setOnClickListener {
            val intent = SessionUtil.getAuthIntent()
            startActivity(intent)
        }

        binding.logoutButton.setOnClickListener {
            viewModel.logout()
        }

        return binding.root
    }
}

我试过的:

我想注入一个模拟OverviewViewModel,这样我就可以隔离Fragment测试,检查按钮单击事件是否与它正确连接。

到目前为止,这是我的测试:

@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class OverviewFragmentTest {
    val hiltRule = HiltAndroidRule(this)

    @get: Rule
    val testRules = RuleChain
        .outerRule(hiltRule)
        .around(ActivityTestRule(MainActivity::class.java))


    val mockViewModel = mockkClass(OverviewViewModel::class)
    val mockIsLogged = MutableLiveData<Boolean>()

    @BindValue @JvmField
    val viewModel: OverviewViewModel = mockViewModel

    @Before
    fun setup () {
        clearAllMocks()
        hiltRule.inject()
    }


@Test
    fun Given_nothing_When_clicking_login_button_Then_login_intent_triggers() {
        every {viewModel.isLogged} returns mockIsLogged
        mockIsLogged.postValue(false)

        Intents.init()
        every { SessionUtil.getAuthIntent() } returns Intent(Intent.ACTION_VIEW, Uri.parse("https://toto"))

        launchFragmentInHiltContainer<OverviewFragment>()
        onView(withId(R.id.login_button)).perform(click())

        verify {
            SessionUtil.getAuthIntent()
        }
        intended(
            hasAction(Intent.ACTION_VIEW)
        )
        intended(
            hasData("https://toto")
        )

        Intents.release()
    }

    @Test
    fun Given_null_response_When_clicking_logout_button_Then_call_toaster() {
        every {viewModel.isLogged} returns mockIsLogged
        mockIsLogged.postValue(true)

        launchFragmentInHiltContainer<OverviewFragment>()

        onView(withId(R.id.logout_button)).perform(click())

        verify {
            mockViewModel.logout()
        }
    }

}

实际:似乎片段仍然使用真实的ViewModel,因为即使发布一个值(例如mockIsLogged.postValue(false)),片段内的观察者仍然记录true(来自真实模型的值)

标签: androiddagger-hiltinstrumented-test

解决方案


推荐阅读