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

使用MediatorLiveData与协程测试ViewModel时遭遇空指针异常

解决ViewModel单元测试中MediatorLiveData.addSource的NullPointerException问题

看起来你遇到的核心问题是Mockito生成的Mock对象默认返回null的LiveData实例,而MediatorLiveData.addSource()方法不接受null参数,导致初始化ViewModel时触发NPE。下面我们一步步分析并解决这个问题:

问题根源拆解

  1. 你的ViewModel中,currentFilter依赖于structureFilterRepository.selectedFilter——这是一个LiveData,但Mock出来的structureFilterRepository默认会给这个属性返回null。当MediatorLiveData执行addSource(currentFilter)时,传入null就会触发堆栈里的SafeIterableMap空指针。
  2. 即使注释掉addSource(query),populate()方法里调用submissionRepository.loadSubmissionBy()返回的也是null的LiveData,同样会在mediator.addSource(result)处触发相同错误。

修复步骤

1. 给Mock对象的LiveData属性/方法返回非null实例

我们需要明确告诉Mockito,当调用这些返回LiveData的方法时,返回一个空的、有效的LiveData对象,而不是默认的null。

2. 处理协程测试环境(可选但推荐)

因为ViewModel使用了viewModelScope,为了避免协程相关的测试问题,建议使用TestCoroutineDispatcher来控制协程执行。

修改后的测试代码示例

@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
class SubmissionViewModelTest: KoinTest {
    @Mock
    private lateinit var structureRepository: StructureRepository
    @Mock
    private lateinit var structureFilterRepository: SubmissionFilterRepository
    @Mock
    private lateinit var submissionRepository: SubmissionRepository

    private val fakeStructures = createFakeStructure(5)
    // 新增测试协程调度器
    private val testDispatcher = TestCoroutineDispatcher()

    @get:Rule
    val rule = InstantTaskExecutorRule()

    @Before
    fun before() {
        MockitoAnnotations.initMocks(this)
        // 1. 设置Mock返回非null的LiveData
        // 给structureFilterRepository.selectedFilter返回空的MutableLiveData
        whenever(structureFilterRepository.selectedFilter)
            .thenReturn(MutableLiveData<SubmissionFilter>())
        // 给submissionRepository.loadSubmissionBy返回空的MutableLiveData
        whenever(submissionRepository.loadSubmissionBy(any(), anyOrNull()))
            .thenReturn(MutableLiveData<List<Submission>>())
        
        // 2. 设置协程主调度器为测试调度器
        Dispatchers.Main.setMain(testDispatcher)

        parentViewModelTest = SubmissionParentViewModel(structureRepository, structureFilterRepository, submissionRepository)
        
        runBlocking(testDispatcher) {
            whenever(structureRepository.loadAllStructures()).thenReturn(fakeStructures)
            parentViewModelTest.init()
        }
    }

    @After
    fun after() {
        // 重置主调度器
        Dispatchers.Main.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test
    fun empty() { }
}

关键修改说明

  • 给Mock的LiveData属性返回有效实例:通过whenever(structureFilterRepository.selectedFilter).thenReturn(MutableLiveData())确保currentFilter不是null,解决初始化时addSource(currentFilter)的NPE。
  • Mock loadSubmissionBy方法返回有效LiveData:确保populate()中调用mediator.addSource(result)时,result不是null。
  • 使用TestCoroutineDispatcher:让ViewModel的viewModelScope协程在测试环境中可控执行,避免潜在的协程异步问题。

这样修改后,你的空测试就能正常通过,同时ViewModel的初始化逻辑和生产环境保持一致。

内容的提问来源于stack exchange,提问作者Louis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 06:40:00