Android 仪器测试在测试使用 RoomDatabase.withTransaction 的挂起函数时冻结
Android instrumented test freezes when it tests a suspend function that uses RoomDatabase.withTransaction
我正在尝试测试以下 LocalDataSource 函数,NameLocalData.methodThatFreezes 函数,但它冻结了。我该如何解决这个问题?或者我怎样才能用另一种方式测试它?
Class待测
class NameLocalData(private val roomDatabase: RoomDatabase) : NameLocalDataSource {
override suspend fun methodThatFreezes(someParameter: Something): Something {
roomDatabase.withTransaction {
try {
// calling room DAO methods here
} catch(e: SQLiteConstraintException) {
// ...
}
return something
}
}
}
测试class
@MediumTest
@RunWith(AndroidJUnit4::class)
class NameLocalDataTest {
private lateinit var nameLocalData: NameLocalData
// creates a Room database in memory
@get:Rule
var roomDatabaseRule = RoomDatabaseRule()
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Before
fun setup() = runBlockingTest {
initializesSomeData()
nameLocalData = NameLocalData(roomDatabaseRule.db)
}
@Test
fun methodThatFreezes() = runBlockingTest {
nameLocalData.methodThatFreezes // test freezes
}
// ... others NameLocalDataTest tests where those functions been tested does not use
// roomDatabase.withTransaction { }
}
Gradle的文件配置
espresso_version = '3.2.0'
kotlin_coroutines_version = '1.3.3'
room_version = '2.2.5'
test_arch_core_testing = '2.1.0'
test_ext_junit_version = '1.1.1'
test_roboletric = '4.3.1'
test_runner_version = '1.2.0'
androidTestImplementation "androidx.arch.core:core-testing:$test_arch_core_testing"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.ext:junit:$test_ext_junit_version"
androidTestImplementation "androidx.test:rules:$test_runner_version"
androidTestImplementation "androidx.test:runner:$test_runner_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
上次我为 Room 数据库编写测试时,我只是简单地使用 runBlock
,它对我有用...
你能看一下 this sample 并检查它是否也适合你吗?
编辑:
哎呀!我错过了这部分......我试过这个(在同一个样本中):
- 我使用
@Transaction
在我的 DAO 中定义了一个虚拟函数
@Transaction
suspend fun quickInsert(book: Book) {
save(book)
delete(book)
}
- 我觉得这才是问题的关键。将
setTransactionExecutor
添加到您的数据库实例化。
appDatabase = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context,
AppDatabase::class.java
).setTransactionExecutor(Executors.newSingleThreadExecutor())
.build()
- 最后,测试成功了
runBlocking
@Test
fun dummyTest() = runBlocking {
val dao = appDatabase.bookDao();
val id = dummyBook.id
dao.quickInsert(dummyBook)
val book = dao.bookById(id).first()
assertNull(book)
}
见this question。
我尝试了很多方法来完成这项工作,使用了 runBlockingTest
、使用了 TestCoroutineScope
、尝试了 runBlocking
、使用了 allowMainThreadQueries
、setTransactionExecutor
和setQueryExecutor
在我的内存数据库中。
但是直到我在 Android Developers Medium 博客的 Threading models in Coroutines and Android SQLite API
文章中找到这个 comment thread 之前,没有任何效果,其他人提到了 运行。作者 Daniel Santiago 说:
I’m not sure what Robolectric might be doing under the hood that could cause withTransaction to never return.
We usually don’t have Robolectric tests but we have plenty of Android Test examples if you want to try that route: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
我能够通过将测试从 Robolectric 测试更改为 Android 测试并使用 runBlocking
来修复我的测试
这是来自 google 来源的示例:
@Before
@Throws(Exception::class)
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
TestDatabase::class.java
)
.build()
booksDao = database.booksDao()
}
@Test
fun runSuspendingTransaction() {
runBlocking {
database.withTransaction {
booksDao.insertPublisherSuspend(
TestUtil.PUBLISHER.publisherId,
TestUtil.PUBLISHER.name
)
booksDao.insertBookSuspend(TestUtil.BOOK_1.copy(salesCnt = 0))
booksDao.insertBookSuspend(TestUtil.BOOK_2)
booksDao.deleteUnsoldBooks()
}
assertThat(booksDao.getBooksSuspend())
.isEqualTo(listOf(TestUtil.BOOK_2))
}
}
我正在尝试测试以下 LocalDataSource 函数,NameLocalData.methodThatFreezes 函数,但它冻结了。我该如何解决这个问题?或者我怎样才能用另一种方式测试它?
Class待测
class NameLocalData(private val roomDatabase: RoomDatabase) : NameLocalDataSource {
override suspend fun methodThatFreezes(someParameter: Something): Something {
roomDatabase.withTransaction {
try {
// calling room DAO methods here
} catch(e: SQLiteConstraintException) {
// ...
}
return something
}
}
}
测试class
@MediumTest
@RunWith(AndroidJUnit4::class)
class NameLocalDataTest {
private lateinit var nameLocalData: NameLocalData
// creates a Room database in memory
@get:Rule
var roomDatabaseRule = RoomDatabaseRule()
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Before
fun setup() = runBlockingTest {
initializesSomeData()
nameLocalData = NameLocalData(roomDatabaseRule.db)
}
@Test
fun methodThatFreezes() = runBlockingTest {
nameLocalData.methodThatFreezes // test freezes
}
// ... others NameLocalDataTest tests where those functions been tested does not use
// roomDatabase.withTransaction { }
}
Gradle的文件配置
espresso_version = '3.2.0'
kotlin_coroutines_version = '1.3.3'
room_version = '2.2.5'
test_arch_core_testing = '2.1.0'
test_ext_junit_version = '1.1.1'
test_roboletric = '4.3.1'
test_runner_version = '1.2.0'
androidTestImplementation "androidx.arch.core:core-testing:$test_arch_core_testing"
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.ext:junit:$test_ext_junit_version"
androidTestImplementation "androidx.test:rules:$test_runner_version"
androidTestImplementation "androidx.test:runner:$test_runner_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
上次我为 Room 数据库编写测试时,我只是简单地使用 runBlock
,它对我有用...
你能看一下 this sample 并检查它是否也适合你吗?
编辑: 哎呀!我错过了这部分......我试过这个(在同一个样本中):
- 我使用
@Transaction
在我的 DAO 中定义了一个虚拟函数
@Transaction
suspend fun quickInsert(book: Book) {
save(book)
delete(book)
}
- 我觉得这才是问题的关键。将
setTransactionExecutor
添加到您的数据库实例化。
appDatabase = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context,
AppDatabase::class.java
).setTransactionExecutor(Executors.newSingleThreadExecutor())
.build()
- 最后,测试成功了
runBlocking
@Test
fun dummyTest() = runBlocking {
val dao = appDatabase.bookDao();
val id = dummyBook.id
dao.quickInsert(dummyBook)
val book = dao.bookById(id).first()
assertNull(book)
}
见this question。
我尝试了很多方法来完成这项工作,使用了 runBlockingTest
、使用了 TestCoroutineScope
、尝试了 runBlocking
、使用了 allowMainThreadQueries
、setTransactionExecutor
和setQueryExecutor
在我的内存数据库中。
但是直到我在 Android Developers Medium 博客的 Threading models in Coroutines and Android SQLite API
文章中找到这个 comment thread 之前,没有任何效果,其他人提到了 运行。作者 Daniel Santiago 说:
I’m not sure what Robolectric might be doing under the hood that could cause withTransaction to never return. We usually don’t have Robolectric tests but we have plenty of Android Test examples if you want to try that route: https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
我能够通过将测试从 Robolectric 测试更改为 Android 测试并使用 runBlocking
这是来自 google 来源的示例:
@Before
@Throws(Exception::class)
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
TestDatabase::class.java
)
.build()
booksDao = database.booksDao()
}
@Test
fun runSuspendingTransaction() {
runBlocking {
database.withTransaction {
booksDao.insertPublisherSuspend(
TestUtil.PUBLISHER.publisherId,
TestUtil.PUBLISHER.name
)
booksDao.insertBookSuspend(TestUtil.BOOK_1.copy(salesCnt = 0))
booksDao.insertBookSuspend(TestUtil.BOOK_2)
booksDao.deleteUnsoldBooks()
}
assertThat(booksDao.getBooksSuspend())
.isEqualTo(listOf(TestUtil.BOOK_2))
}
}