androidkotlinmockitomockk

How do I mock a File.copyTo in Mockito and Kotlin


Simply put, I've got a File object that product code will invoke the copyTo method on. Similarly, I'm looking for an equivalent mocking technique for File.inputStream

In the unit test, I just want a mock file and the copyTo call to be a no-op or at best verified.

Simple example:

    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")
    }

When the unit test runs, this exception is thrown:

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

I believe the problem may be related to the fact that copyTo seems to be an extension function.

What's the easiest way to just make copyTo a no-op in the unit test that always succeeds in the unit test?

mockito or mockk solutions are acceptable.


Solution

  • The problem you're having is that File.copyTo is a Kotlin extension function. Mockito is a Java framework, and doesn't understand anything about Kotlin extension functions.

    Java doesn't support extension functions, so what File.copyTo actually compiles to in JVM bytecode is a static function, a member of class kotlin.io.UtilsKt, which takes an extra argument at the start of the argument list, representing the receiver object (the source file), like this:

    public static final File copyTo(
            File $this$copyTo,
            File target,
            boolean overwrite,
            int bufferSize)
    

    If you want to use Mockito to mock this, you'll need to use mockStatic on the kotlin.io.UtilsKt class and then mock the static method copyTo. It would be much easier to switch to a mocking framework that is written for Kotlin, such as Mockk, if that is possible.

    When using Mockk, to mock module-wide extension functions, use mockkStatic as per the instructions on the Mockk website. Here's the equivalent of your original code, translated to use Mockk:

    import io.mockk.every
    import io.mockk.mockk
    import io.mockk.mockkStatic
    import org.junit.jupiter.api.Assertions.*
    import org.junit.jupiter.api.Test
    import java.io.File
    
    class MyTest {
        @Test
        fun copyFileTest() {
            println("start test")
            val mockFileSrc = mockk<File>()
            val mockFileDst = mockk<File>()
    
            mockkStatic(File::copyTo)
            every { mockFileSrc.exists() } returns true
            every { mockFileSrc.copyTo(any(), any(), any()) } answers {
                arg(1) as File  // arg(0) is `this` (source), arg(1) is dest
            }
    
            println("done initializing mocks")
    
            assertTrue(mockFileSrc.exists())
            mockFileSrc.copyTo(mockFileDst, true, 0)
    
            println("done with test")
        }
    }