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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 09:55:14