我很难在Android上进行仪器化测试。
目标:在ViewModel
仪器化测试中注入模拟的Fragment
。
上下文:
我的ViewModel
使用Hilt Jetpack integrations和@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)
)时,该片段内的观察者仍会记录{{ 1}}(值来自真实模型)