kotlinunit-testingmockingextension-methodsmockk

Mock extension function in kotlin-stdlib


I have a method to download content from streaming end point. I want to write unit test this method. What I faced a problem is I need to mock kotlin extension method file.outputStream() but I can't mock outputStream() which is defined in kotlin-stdlib FileReadWrite File as like below

@kotlin.internal.InlineOnly
public inline fun File.outputStream(): FileOutputStream {
    return FileOutputStream(this)
}

My download method is like below

override fun download(url: String, targetFile: File): Single<File> {
    return Single.fromCallable {
            fileDownloadApi
            .downloadFile(url)
            .execute()
            .body()?.byteStream()?.use { input ->
                targetFile.outputStream().use { output ->
                    FileUtil.copyFile(input, output)
                    return@fromCallable targetFile
                }
            }
        targetFile
    }
}

I need to mock this part targetFile.outputStream() . I am passing mock targetFile

My test code is like bolow

val mockFileOutputStream = mockk<FileOutputStream>()
val mockTargetFile = mockk<File>()
mockkStatic("kotlin.io.FilesKt")  // declares as JvmName @file:JvmName("FilesKt")
every { File("abc").outputStream() } returns mockFileOutputStream

service.download("", mockTargetFile)
    .subscribe(testObserver)

I mocked file.outputStream() like official doc suggested but getting error

io.mockk.MockKException: Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock

for line every { File("abc").outputStream() } returns mockFileOutputStream

I can't figureout what i'm missing. Please help me to figure out this issue.


Solution

  • Notice that the extension function is inline. This means that when you call it, there is no actual call to the extension method declared in FilesKt. Instead, the compiler generates code that directly calls the constructor of FileOutputStream, since that is the implementation of outputStream.

    So what you should be mocking is the constructor of FileOutputStream, not FilesKt.

    For example,

    mockkConstructor(FileOutputStream::class) {
        // write the mocks that you would have written for mockFileOutputStream here
        every { anyConstructed<FileOutputStream>().write(...) } just runs
        // and you don't need mockFileOutputStream anymore
    }