如何正确测试Android Fragment中选中的主题为浅色还是深色模式
问题原因
- 断言使用的是测试启动时缓存的旧
selectedTheme变量值,调用主题切换方法后没有重新从LiveData获取最新值,导致断言永远和初始值对比,自然会随初始主题的不同交替报错。
- 断言使用的是测试启动时缓存的旧
changeSelectedTheme方法内部通过viewModelScope.launch异步执行存储逻辑,调用方法后直接断言,协程可能还未执行完成,存储和LiveData更新都未生效。
- 自行实例化的
StorageViewModel和Fragment内部使用的ViewModel不是同一个实例,调用自行创建的实例的更新方法,不会同步到Fragment的生命周期和数据状态。
- 自行实例化的
- 夜间模式配置参数写反:
AppCompatDelegate.MODE_NIGHT_NO对应浅色模式,MODE_NIGHT_YES才对应深色模式,当前切换逻辑的配置和预期目标完全相反。
- 夜间模式配置参数写反:
- 测试逻辑冗余:模拟点击图标后又手动实现了一遍主题切换逻辑,正常测试只需要验证Fragment自身的点击事件逻辑是否符合预期,不需要在测试层重复写业务代码。
修复方案
第一步:统一测试初始状态
先在测试启动时固定初始主题,避免每次运行初始状态不一致导致结果不稳定,同时确保使用的DataStorage是测试用的非持久化实现。
第二步:修正测试逻辑
不要自行创建ViewModel,直接从Fragment获取实例,断言时重新读取LiveData的最新值,等待异步操作完成。
修正后的HomeFragmentTest.kt
@HiltAndroidTest @UninstallModules(StorageModule::class) class HomeFragmentTest{ @get:Rule(order = 0) var hiltAndroidRule = HiltAndroidRule(this) @get:Rule(order = 1) var instantTaskExecutorRule = InstantTaskExecutorRule() @Inject lateinit var dataStorage: DataStorage @Before fun setUp(){ hiltAndroidRule.inject() // 固定初始主题为浅色,保证测试可复现 runBlocking { dataStorage.setSelectedTheme("light") } } @Test fun testThatThemeIsChangedToDarkWhenInitialIsLight(){ var fragment: HomeFragment? = null // 启动Fragment并获取实例 launchFragmentInHiltContainer<HomeFragment> { fragment = this } // 获取Fragment内部的ViewModel,保证是同一个实例 val storageViewModel = ViewModelProvider(fragment!!)[StorageViewModel::class.java] // 验证初始主题为浅色 val initialTheme = storageViewModel.selectedTheme.getOrAwaitValue() assertThat(initialTheme).isEqualTo("light") // 模拟点击切换按钮 onView(withId(R.id.ic_theme_mode)).perform(click()) // 等待LiveData更新,获取最新主题值断言 val updatedTheme = storageViewModel.selectedTheme.getOrAwaitValue() // 初始是浅色,点击后应该是深色 assertThat(updatedTheme).isEqualTo("dark") // 验证系统夜间模式是否正确切换 assertThat(AppCompatDelegate.getDefaultNightMode()).isEqualTo(AppCompatDelegate.MODE_NIGHT_YES) } // 单独写测试用例覆盖初始是深色的场景 @Test fun testThatThemeChangesToLightWhenInitialIsDark(){ runBlocking { dataStorage.setSelectedTheme("dark") } var fragment: HomeFragment? = null launchFragmentInHiltContainer<HomeFragment> { fragment = this } val storageViewModel = ViewModelProvider(fragment!!)[StorageViewModel::class.java] val initialTheme = storageViewModel.selectedTheme.getOrAwaitValue() assertThat(initialTheme).isEqualTo("dark") onView(withId(R.id.ic_theme_mode)).perform(click()) val updatedTheme = storageViewModel.selectedTheme.getOrAwaitValue() assertThat(updatedTheme).isEqualTo("light") assertThat(AppCompatDelegate.getDefaultNightMode()).isEqualTo(AppCompatDelegate.MODE_NIGHT_NO) } }
第三步:确保Fragment内部逻辑正确
需要确认HomeFragment的点击事件里已经实现了正确的切换逻辑,不需要在测试里重复写,示例逻辑参考:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val storageViewModel = ViewModelProvider(this)[StorageViewModel::class.java] view.findViewById<ImageView>(R.id.ic_theme_mode).setOnClickListener { val currentTheme = storageViewModel.selectedTheme.value if (currentTheme == "light") { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) storageViewModel.changeSelectedTheme("dark") } else { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) storageViewModel.changeSelectedTheme("light") } } }
内容的提问来源于stack exchange,提问作者Ibramazin
相关产品推荐
相关产品推荐

