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

Android(Kotlin)中Realm保存成功但测试断言失败问题排查

问题:Realm数据库协程测试中保存成功但isEmpty()断言失败

问题描述

在Android应用中使用Kotlin协程测试Realm数据库,实现了包含isEmpty()方法的ConnectionsRepository类,编写单元测试断言保存Connection对象前后的行为。尽管日志显示保存成功,但测试仍抛出AssertionError失败。

方法代码

// ConnectionsRepository
override fun isEmpty(): Boolean {
    return RealmManager.getDefaultInstance().use { realm ->
        realm.where(Connection::class.java).count() == 0L
    }
}

suspend fun Connection.save(): Connection? = ConnectionsRepository.saveModel(this) 

override suspend fun saveModel(connection: Connection): Connection? {
    if (connection.createdAt == 0L) connection.createdAt = 
        DateTime.now().withZone(DateTimeZone.UTC).millis
    connection.updatedAt = DateTime.now().withZone(DateTimeZone.UTC).millis

    return withContext(Dispatchers.IO) {
        var savedConnection: Connection? = null
        RealmManager.getDefaultInstance().use { realmDb ->
            realmDb.executeTransaction { transaction ->
                Log.d("RealmTransaction", "Saving connection: $connection")
                savedConnection = realmDb.copyFromRealm(transaction.copyToRealmOrUpdate(connection))
            }
        }
        if (savedConnection == null) {
            Log.e("Error", "Connection was saved with error")
        } else {
            Log.e("Error", "Connection was saved successfully: $savedConnection")
        }
        savedConnection
    }
}

测试代码

@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class ConnectionsRepositoryTest : DatabaseTestCase() {

    @Test
    @Throws(Exception::class)
    fun isEmptyTest() = runBlockingTest {
        Assert.assertTrue(ConnectionsRepository.isEmpty())

        runBlocking {
            Connection().setGuid("guid1").setId("1").setAccessToken("token4").save()
            advanceUntilIdle()  // Wait for coroutine completion
        }

        Assert.assertFalse(ConnectionsRepository.isEmpty()) 
    } 
} 

日志信息

04-04 16:55:47.713 22806 22836 D RealmTransaction: Saving connection: Connection(...)
04-04 16:55:47.716 22806 22836 E Error: Connection was saved successfully: Connection(...)

已做排查

  • 日志确认Connection对象保存成功
  • 使用advanceUntilIdle()等待协程完全执行完毕

核心疑问

为何测试仍出现AssertionError失败?是否是Realm缓存或同步问题?


问题分析与解决方案

1. 嵌套runBlocking导致协程调度异常

测试代码中在runBlockingTest内部嵌套runBlocking,会破坏TestDispatcher的调度逻辑:runBlockingTest使用测试专用协程调度器,而嵌套的runBlocking会切换到默认调度器,导致advanceUntilIdle()无法正确等待保存操作完成。

修复: 移除嵌套的runBlocking,直接在runBlockingTest作用域内调用save():

@Test
@Throws(Exception::class)
fun isEmptyTest() = runBlockingTest {
    Assert.assertTrue(ConnectionsRepository.isEmpty())

    Connection().setGuid("guid1").setId("1").setAccessToken("token4").save()
    advanceUntilIdle()

    Assert.assertFalse(ConnectionsRepository.isEmpty()) 
} 

2. Realm线程绑定与实例可见性问题

Realm实例是线程绑定的,若RealmManager.getDefaultInstance()返回的实例配置不一致(比如意外创建多个内存数据库实例),会导致主线程的isEmpty()看不到IO线程保存的数据。

排查与修复:

  • 检查RealmManager.getDefaultInstance()实现,确保测试环境下所有操作使用同一个Realm配置(比如内存数据库)。
  • 在isEmpty()中添加日志打印实际计数,确认数据是否真的未写入:
    override fun isEmpty(): Boolean {
        return RealmManager.getDefaultInstance().use { realm ->
            val count = realm.where(Connection::class.java).count()
            Log.d("RealmDebug", "Current connection count: $count")
            count == 0L
        }
    }
    

3. 协程Dispatcher硬编码问题

saveModel中直接使用Dispatchers.IO,导致测试时无法替换为测试调度器,增加协程调度不确定性。

改进: 通过构造函数注入Dispatcher,便于测试替换:

class ConnectionsRepository(
    private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) {
    override suspend fun saveModel(connection: Connection): Connection? {
        // ...
        return withContext(dispatcher) {
            // ...
        }
    }
}

测试时注入TestCoroutineDispatcher:

class ConnectionsRepositoryTest : DatabaseTestCase() {
    private val testDispatcher = TestCoroutineDispatcher()
    private lateinit var repository: ConnectionsRepository

    @Before
    fun setup() {
        repository = ConnectionsRepository(testDispatcher)
    }

    @After
    fun teardown() {
        testDispatcher.cleanupTestCoroutines()
    }
}

4. 测试隔离问题

若DatabaseTestCase未在每次测试前清理Realm数据库,前一次测试的数据残留会影响断言结果。

改进: 在@Before方法中清理数据库:

@Before
fun setup() {
    RealmManager.getDefaultInstance().use { realm ->
        realm.executeTransaction { it.deleteAll() }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:52:42