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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 16:10:31