我很难在 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
(来自真实模型的值)