如何让Compose截图测试读取screenshotTest上下文外的跨模块Assets
项目结构
- :app模块:仅负责启动功能界面
- :feature:chat模块:包含完整功能代码、Composable组件及对应预览,截图测试代码路径为
feature\chat\src\screenshotTest\...\PreviewScreenshotCode.kt - :core模块:存放预览富内容所需的json、text文件,通过
context.resources.assets.open(...)读取
问题现象
正常Compose预览可正常读取:core模块的Assets资源并渲染UI,但截图测试时无法访问该模块Assets资源,推测是screenshotTest上下文与:core模块Assets环境差异导致,尝试修改上下文未解决。
方案A:让截图库访问其他模块的Assets资源
截图测试默认使用当前测试模块(:feature:chat)的上下文,无法直接跨模块读取Assets,可通过两种方式解决:
复制:core模块Assets到测试模块
在:feature:chat模块的build.gradle.kts中添加Gradle任务,将:core模块的Assets复制到当前模块的screenshotTest资源目录:tasks.register<Copy>("copyCoreAssetsToScreenshotTest") { from(project(":core").file("src/main/assets")) into("src/screenshotTest/assets") dependsOn(":core:assemble") } // 让截图测试任务依赖该复制任务 tasks.withType<Test>().configureEach { dependsOn("copyCoreAssetsToScreenshotTest") }执行任务后,截图测试上下文即可通过原有代码读取资源。
使用全局Instrumentation上下文
替换原有上下文为InstrumentationRegistry提供的全局上下文,该上下文可访问所有模块合并后的Assets:val context = InstrumentationRegistry.getInstrumentation().targetContext val inputStream = context.resources.assets.open("sample.json")需确保:core模块的
build.gradle中正确配置了Assets目录:android { sourceSets { main { assets.srcDirs("src/main/assets") } } }
方案B:自动生成包含资源内容的Kotlin Object
针对大量json/text文件,可通过Gradle任务自动生成包含资源内容的代码,无需上下文访问:
添加Gradle生成任务
在:core模块的build.gradle.kts中注册自定义任务:tasks.register<JavaExec>("generateAssetObjects") { mainClass.set("com.yourcorepackage.AssetCodeGenerator") classpath = sourceSets.main.runtimeClasspath args = listOf( projectDir.resolve("src/main/assets").absolutePath, projectDir.resolve("src/main/java/com/yourcorepackage/generated").absolutePath ) } // 让编译任务依赖生成任务 tasks.compileKotlin.dependsOn("generateAssetObjects")编写资源生成工具类
创建AssetCodeGenerator.kt,遍历Assets目录读取文件内容并生成Kotlin Object:package com.yourcorepackage import java.io.File fun main(args: Array<String>) { val assetsDir = File(args[0]) val outputDir = File(args[1]) outputDir.mkdirs() val code = buildString { append("package com.yourcorepackage.generated\n\n") append("object AssetContents {\n") // 遍历所有json/text文件 assetsDir.walk().filter { it.isFile && it.extension in listOf("json", "text") }.forEach { file -> val propertyName = file.nameWithoutExtension.replace("-", "_").replace(".", "_") val escapedContent = file.readText() .replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") append(" const val $propertyName = \"$escapedContent\"\n") } append("}\n") } File(outputDir, "AssetContents.kt").writeText(code) }使用生成的代码
执行generateAssetObjects任务后,直接通过AssetContents.sample_json(对应sample.json)访问资源内容,无需上下文:val sampleData = AssetContents.sample_json注意:大体积资源不建议用此方案,会增加代码包体积;需确保文件名转义为合法的Kotlin变量名。
内容的提问来源于stack exchange,提问作者Shadow

