6

I got some code which I want to test using MockK as a mocking library:

fun loadImage(imageLoader: coil.ImageLoader) {
    val request = ImageRequest.Builder(imageView.context)
        .data(url)
        .crossfade(true)
        .build()
    imageLoader.enqueue(request)
}

I am able to mock this using a hierarchical approach:


val request: ImageRequest = mockk()
val imageLoader: coil.ImageLoader = mockk(relaxed = true)
mockkConstructor(ImageRequest.Builder::class)
every { anyConstructed<ImageRequest.Builder>().data(any()) } returns mockk {
    every { crossfade(any<Boolean>()) } returns mockk {
        every { build() } returns request
    }
}

loadImage(imageLoader)

verify { imageLoader.enqueue(request) }

So this is problematic since I also test the order in which the builder functions are called. When I would switch the .data and .crossfade calls the test would break while the implementation still works.

Is there a better way to apprach this?

Henning
  • 2,202
  • 1
  • 17
  • 38

1 Answers1

0

I think what you're looking for is answers { this.self as ImageRequest.Builder } instead of returns mockk().

However, for multiple builder functions it gets pretty tedious/repetitive with many calls to anyConstructed...answers{}. If you define this function somewhere:

    inline fun <reified T : Any> mockBuilder(crossinline block: MockKMatcherScope.(T) -> T) {
        every { block(anyConstructed<T>()) } answers {
            this.self as T
        }
    }

you can use it like:

    mockkConstructor(ImageRequest.Builder::class)
    every { anyConstructed<ImageRequest.Builder>().build() } returns mockk()
    mockBuilder<ImageRequest.Builder> { it.data(any()) }
    mockBuilder<ImageRequest.Builder> { it.crossfade(any<Boolean>()) }
    ...

It'll work with capturing also, if you want to know how you are building out that ImageRequest.

nick18702
  • 54
  • 6