I am trying to test some download code involving OkHttp3 and failing miserably. Goal: test downloading an image file and verify it worked. Platform: Android. This code is working in production but the test code just isn't making any sense.
Prod code
class FileDownloaderImpl internal constructor(
private val ioScheduler: Scheduler,
private val logger: LoggingInterceptor,
private val parser: ((String) -> HttpUrl)? // for testing only
) : FileDownloader {
@Inject constructor(logger: LoggingInterceptor) : this(Schedulers.io(), logger, null)
override fun downloadFile(url: String, destination: File): Single<File> {
Logger.d(TAG, "downloadFile\nurl = $url\nfile = $destination")
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
val call = client.newCall(newRequest(url))
return Single.fromCallable { call.execute() }
.doOnDispose { call.cancel() }
.subscribeOn(ioScheduler)
.map { response ->
Logger.d(TAG, "Successfully downloaded board: $response")
return@map response.body()!!.use { body ->
Okio.buffer(Okio.sink(destination)).use { sink ->
sink.writeAll(body.source())
}
destination
}
}
}
/**
* Creates the request, optionally parsing the URL into an [HttpUrl]. The primary (maybe only)
* use-case for that is for wrapping the URL in a `MockWebServer`.
*/
private fun newRequest(url: String): Request {
val httpUrl = parser?.invoke(url)
val builder = Request.Builder()
httpUrl?.let { builder.url(it) } ?: builder.url(url)
return builder.build()
}
}
Test code (JUnit5)
@ExtendWith(TempDirectory::class)
internal class FileDownloaderImplTest {
private val mockWebServer = MockWebServer()
private val logger = LoggingInterceptor(HttpLoggingInterceptor.Level.BODY) { msg -> println(msg) }
private val fileDownloader = FileDownloaderImpl(Schedulers.trampoline(), logger) {
mockWebServer.url("/$it")
}
@BeforeEach fun setup() {
mockWebServer.start()
}
@AfterEach fun teardown() {
mockWebServer.shutdown()
}
@Test fun downloadFile(@TempDir tempDirectory: Path) {
// Given
val res = javaClass.classLoader.getResource("green20.webp")
val f = File(res.path)
val buffer = Okio.buffer(Okio.source(f)).buffer()
mockWebServer.enqueue(MockResponse().setBody(buffer))
val destFile = tempDirectory.resolve("temp.webp").toFile()
// Verify initial condition
destFile.exists() shouldBe false
// When
fileDownloader.downloadFile("test.html", destFile)
// Then
.test()
.assertValue { file ->
file.exists() shouldBe true
file.length() shouldEqualTo 66 // FAIL: always 0
true
}
}
}
More detail
"green20.webp" is a file that exists in app/test/resources. When I debug, all indications are that it exists. On the subject of debugging, I have breakpoints in the prod code and it looks like the Response object (presumably a MockResponse) has no body. I have no idea why that would be.
Current ideas:
- I'm not adding a mock response body correctly
- The file is somehow "open" and so its length is always 0 even though it is not actually empty.
EDIT
I tried removing the MockWebServer from the test and initiated a real download, and my test actually passed. So, I think I'm doing something wrong with the MockResponse and its body. Any help would be much appreciated.
For reasons that are unclear to me,
Okio.buffer(Okio.source(file)).buffer()always returned an emptyBuffer. The following, however, works:What I'm doing now is creating a new buffer manually and writing the entire file into it. Now my
MockResponsehas a real body.I would still love for someone to explain the why of this....