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

如何在Mockito与Kotlin中Mock File.copyTo及File.inputStream方法?

解决Kotlin File.copyTo扩展函数的Mock问题

问题场景

业务代码中调用了File对象的copyTo方法,同时需要MockFile.inputStream,单元测试希望将这些操作设为成功的空操作,或仅验证调用行为。用户尝试用Mockito Mock File对象时抛出NPE,测试代码如下:

fun copyFileTest() {
    println("start test")
    val mockFileSrc = mock(File::class.java)
    val mockFileDst = mock(File::class.java)

    `when`(mockFileSrc.exists()).doReturn(true)
    `when`(mockFileSrc.copyTo(any(), any(), any())).thenAnswer { // DOES NOT WORK
        val result = it.arguments[0]
        result as File
    }

    println("done initializing mocks")

    Assert.assertEquals(mockFileSrc.exists(), true)
    mockFileSrc.copyTo(mockFileDst, true, 0)

    println("done with test")
}

运行时抛出异常:

Parameter specified as non-null is null: method kotlin.io.FilesKt__UtilsKt.copyTo, parameter target
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.io.FilesKt__UtilsKt.copyTo, parameter target
    at kotlin.io.FilesKt__UtilsKt.copyTo(Utils.kt)
    at com.selibe.myapp.foo.WorkerTest.copyFileTest(WorkerTest.kt:121) <34 internal lines>
    at jdk.proxy2/jdk.proxy2.$Proxy5.processTestClass(Unknown Source) <7 internal lines>
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

问题原因

copyTo是Kotlin扩展函数,并非File类的成员方法。实际调用时会转为调用kotlin.io.FilesKt__UtilsKt.copyTo(File, File, Boolean, Int)静态方法,直接MockFile对象无法拦截这个调用。另外Mockito的any()在Kotlin非空参数场景下会返回null,触发NPE。

解决方案

方案1:用MockK(推荐,对Kotlin支持更友好)

MockK原生支持扩展函数Mock,无需处理静态方法,代码更简洁:

import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Test
import java.io.File

class WorkerTest {
    @Test
    fun copyFileTest() {
        val mockFileSrc = mockk<File>()
        val mockFileDst = mockk<File>()

        // Mock exists方法
        every { mockFileSrc.exists() } returns true
        // Mock copyTo扩展函数,设为成功空操作并返回目标文件
        every { mockFileSrc.copyTo(mockFileDst, true, 0) } returns mockFileDst
        // 匹配任意参数的写法(MockK的any()不会返回null)
        every { mockFileSrc.copyTo(any(), any(), any()) } returns mockFileDst

        assertEquals(mockFileSrc.exists(), true)
        val result = mockFileSrc.copyTo(mockFileDst, true, 0)
        
        // 验证调用(可选)
        verify { mockFileSrc.copyTo(mockFileDst, true, 0) }
        assertEquals(result, mockFileDst)
    }

    // Mock File.inputStream扩展函数示例
    @Test
    fun mockFileInputStream() {
        val mockFile = mockk<File>()
        val mockInputStream = mockk<java.io.InputStream>()
        
        every { mockFile.inputStream() } returns mockInputStream
        // 后续调用mockFile.inputStream()会返回Mock的流
    }
}

方案2:用Mockito(需支持静态Mock)

Mockito 3.4.0+支持静态方法Mock,需要借助mockito-inline依赖,步骤如下:

  1. 确保依赖中包含mockito-inline(代替普通mockito-core)
  2. 静态MockFilesKt__UtilsKt类,拦截copyTo调用:
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.Mockito.*
import java.io.File
import kotlin.io.FilesKt__UtilsKt.copyTo

class WorkerTest {
    @Test
    fun copyFileTest() {
        val mockFileSrc = mock(File::class.java)
        val mockFileDst = mock(File::class.java)

        `when`(mockFileSrc.exists()).thenReturn(true)
        
        // 开启静态Mock并限定作用域
        mockStatic(FilesKt__UtilsKt::class.java) {
            // 拦截指定参数的copyTo调用,返回目标文件
            `when`(copyTo(mockFileSrc, mockFileDst, true, 0)).thenReturn(mockFileDst)
            // 匹配任意参数的写法
            `when`(copyTo(any(File::class.java), any(File::class.java), anyBoolean(), anyInt())).thenReturn(mockFileDst)

            assertEquals(mockFileSrc.exists(), true)
            val result = mockFileSrc.copyTo(mockFileDst, true, 0)
            
            // 验证静态方法调用
            verify(FilesKt__UtilsKt::class.java).copyTo(mockFileSrc, mockFileDst, true, 0)
            assertEquals(result, mockFileDst)
        }
    }

    // Mock File.inputStream扩展函数示例(同样是静态Mock)
    @Test
    fun mockFileInputStream() {
        val mockFile = mock(File::class.java)
        val mockInputStream = mock(java.io.InputStream::class.java)
        
        mockStatic(kotlin.io.FilesKt__UtilsKt::class.java) {
            `when`(kotlin.io.FilesKt__UtilsKt.inputStream(mockFile)).thenReturn(mockInputStream)
            // 调用mockFile.inputStream()会返回Mock的流
        }
    }
}

关键注意点

  • MockK的any()不会返回null,适配Kotlin非空参数,避免NPE
  • Mockito静态Mock需要在mockStatic代码块内执行测试逻辑,确保静态Mock作用域正确
  • 两种方案都可以实现copyTo空操作(仅验证调用)或返回指定结果,按需调整returns或thenAnswer

内容的提问来源于stack exchange,提问作者selbie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 23:27:26