You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确测试Android Fragment中选中的主题为浅色还是深色模式

问题原因
    1. 断言使用的是测试启动时缓存的旧selectedTheme变量值,调用主题切换方法后没有重新从LiveData获取最新值,导致断言永远和初始值对比,自然会随初始主题的不同交替报错。
    1. changeSelectedTheme方法内部通过viewModelScope.launch异步执行存储逻辑,调用方法后直接断言,协程可能还未执行完成,存储和LiveData更新都未生效。
    1. 自行实例化的StorageViewModel和Fragment内部使用的ViewModel不是同一个实例,调用自行创建的实例的更新方法,不会同步到Fragment的生命周期和数据状态。
    1. 夜间模式配置参数写反:AppCompatDelegate.MODE_NIGHT_NO对应浅色模式,MODE_NIGHT_YES才对应深色模式,当前切换逻辑的配置和预期目标完全相反。
    1. 测试逻辑冗余:模拟点击图标后又手动实现了一遍主题切换逻辑,正常测试只需要验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 04:36:03