Android保存文件用例单元测试:FileNotFoundException问题及测试方法
问题分析与解决方案
你遇到的FileNotFoundException,核心原因是单元测试中使用的Context未正确模拟filesDir:真实设备上context.filesDir是系统分配的合法内部存储目录,但单元测试中Mock的Context默认不会返回有效存在的目录,导致拼接出的路径无法创建文件。
下面是针对这个用例的单元测试实现方案:
第一步:优化原代码(可选但更健壮)
先把路径拼接改成更安全的方式,避免平台分隔符差异问题:
// 替换原路径拼接逻辑 val file = File(context.filesDir, "issues.csv") val fos = FileOutputStream(file)
第二步:编写单元测试
这里采用MockK模拟依赖、kotlinx-coroutines-test处理协程,实现贴近真实场景的测试:
1. 添加测试依赖(build.gradle.kts)
testImplementation("io.mockk:mockk:1.13.8") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") testImplementation("junit:junit:4.13.2")
2. 测试类实现
import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import okhttp3.ResponseBody import org.junit.After import org.junit.Before import org.junit.Test import java.io.File @OptIn(ExperimentalCoroutinesApi::class) class SaveFileUseCaseTest { private val mockContext = mockk<Context>() private val testDispatcher = UnconfinedTestDispatcher() private lateinit var useCase: SaveFileUseCase private lateinit var testDir: File @Before fun setup() { // 配置协程测试调度器 Dispatchers.setMain(testDispatcher) // 创建临时测试目录 testDir = File(System.getProperty("java.io.tmpdir"), "test_save_file") testDir.mkdirs() // 模拟Context的filesDir指向临时目录 every { mockContext.filesDir } returns testDir // 初始化用例 useCase = SaveFileUseCase(mockContext, testDispatcher) } @After fun teardown() { // 清理测试残留文件 testDir.deleteRecursively() Dispatchers.resetMain() } @Test fun `should write input stream content to target file`() = runTest { // 准备测试数据 val testContent = "issue_id,title\n1,Test Issue\n2,Another Issue" val mockResponseBody = mockk<ResponseBody>() every { mockResponseBody.byteStream() } returns testContent.byteInputStream() // 执行用例 useCase.invoke(mockResponseBody) // 验证结果 val targetFile = File(testDir, "issues.csv") assert(targetFile.exists()) assert(targetFile.readText() == testContent) } @Test fun `should do nothing when response body is null`() = runTest { // 传入null执行用例 useCase.invoke(null) // 验证未创建文件 val targetFile = File(testDir, "issues.csv") assert(!targetFile.exists()) } }
测试关键说明
- 真实目录模拟:用系统临时目录替代Mock的空路径,确保文件能正常创建,覆盖真实读写逻辑。
- 协程控制:使用测试专用调度器,避免异步执行导致的测试时序问题。
- 双向验证:既验证文件是否生成,也检查文件内容是否与输入一致,确保业务逻辑正确。
- 资源清理:测试完成后删除临时目录,避免残留垃圾文件。
内容的提问来源于stack exchange,提问作者noloman
相关产品推荐
相关产品推荐

