如何在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依赖,步骤如下:
- 确保依赖中包含
mockito-inline(代替普通mockito-core) - 静态Mock
FilesKt__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
相关产品推荐
相关产品推荐

