Android 33中如何Mock ContentResolver?MediaStore测试查询为空
解决MediaStore测试中插入后查询为空的问题
问题根源
你直接操作真实MediaStore的方式在测试环境中行不通:
- 单元测试里没有真实的系统ContentProvider,插入操作根本没写入数据
- Instrumented测试中,
MediaStore受系统权限和沙箱限制,手动插入的内容不会被真实Provider识别
官方推荐的两种可行方案如下:
方案一:自定义Fake ContentProvider
创建一个内存版的Fake MediaStore Provider,完全控制数据的插入和查询:
1. 实现FakeMediaStoreProvider
class FakeMediaStoreProvider : ContentProvider() { private val db = SQLiteDatabase.create(null).apply { execSQL(""" CREATE TABLE audio ( ${MediaStore.Audio.Media._ID} INTEGER PRIMARY KEY, ${MediaStore.Audio.Media.DISPLAY_NAME} TEXT, ${MediaStore.Audio.Media.IS_MUSIC} INTEGER ) """.trimIndent()) } override fun onCreate(): Boolean = true override fun insert(uri: Uri, values: ContentValues?): Uri? { val id = db.insert("audio", null, values) return Uri.parse("content://fake.media/audio/$id") } override fun query( uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String? ): Cursor? { return db.query( "audio", projection, selection, selectionArgs, null, null, sortOrder ) } // 其他未用到的方法返回默认值即可 override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int = 0 override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0 override fun getType(uri: Uri): String? = "vnd.android.cursor.dir/audio" }
2. 在测试中注册并使用
@RunWith(AndroidJUnit4::class) class MusicStoreTest { private lateinit var fakeResolver: ContentResolver @Before fun setup() { // 注册Fake Provider val context = ApplicationProvider.getApplicationContext<Context>() val authority = "fake.media" context.contentResolver.registerContentProvider( authority, FakeMediaStoreProvider::class.java.name, null ) fakeResolver = context.contentResolver } @Test fun getMusicFromMediaStore_returnsExpectedList() { // 插入测试数据 val values = ContentValues().apply { put(MediaStore.Audio.Media._ID, 1L) put(MediaStore.Audio.Media.DISPLAY_NAME, "test.mp3") put(MediaStore.Audio.Media.IS_MUSIC, 1) } fakeResolver.insert(Uri.parse("content://fake.media/audio"), values) // 调用待测试方法 val result = getMusicFromMediaStore(fakeResolver) // 断言结果 assertEquals(1, result.size) assertEquals("test.mp3", result[0].displayName) } }
方案二:用Mockito Mock ContentResolver
如果不需要测试真实Provider的逻辑,直接Mock查询结果更高效:
1. 构造Mock Cursor
private fun createMockCursor(): Cursor { val cursor = mock(Cursor::class.java) // 模拟Cursor包含一行数据 `when`(cursor.count).thenReturn(1) `when`(cursor.moveToFirst()).thenReturn(true) // 模拟列索引 val idIndex = 0 val nameIndex = 1 val isMusicIndex = 2 `when`(cursor.getColumnIndex(MediaStore.Audio.Media._ID)).thenReturn(idIndex) `when`(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME)).thenReturn(nameIndex) `when`(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC)).thenReturn(isMusicIndex) // 模拟数据返回 `when`(cursor.getLong(idIndex)).thenReturn(1L) `when`(cursor.getString(nameIndex)).thenReturn("test.mp3") `when`(cursor.getInt(isMusicIndex)).thenReturn(1) // 模拟关闭操作 `when`(cursor.close()).thenReturn(Unit) return cursor }
2. 编写测试用例
@Test fun getMusicFromMediaStore_returnsExpectedList() { val mockResolver = mock(ContentResolver::class.java) val mockCursor = createMockCursor() // 配置Mock:当调用query时返回构造好的Cursor `when`(mockResolver.query( eq(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), any(), any(), any(), any() )).thenReturn(mockCursor) // 调用待测试方法 val result = getMusicFromMediaStore(mockResolver) // 断言结果 assertEquals(1, result.size) assertEquals("test.mp3", result[0].displayName) // 验证Cursor被关闭 verify(mockCursor).close() }
注意事项
- 单元测试优先用Mock方案,速度快且无需依赖Android环境
- 如果需要测试Provider的查询逻辑(比如筛选、排序),用Fake Provider更贴近真实场景
- 避免在测试中操作真实
MediaStore,系统限制会导致测试不稳定
内容的提问来源于stack exchange,提问作者John Glen
相关产品推荐
相关产品推荐

