2

I have a function which uses a field in DisplayMetrics of Resources of Context class:

fun getIconForDevice(context: Context, iconUrl: String): String {
    val metrics = context.resources.displayMetrics
    var suffix = ""
    //below checks MUST be in this increasing order or it may failed
    if (metrics.densityDpi <= DisplayMetrics.DENSITY_MEDIUM)
        suffix = "-m"
    else if (metrics.densityDpi <= DisplayMetrics.DENSITY_HIGH)
        suffix = "-h"
    else if (metrics.densityDpi <= DisplayMetrics.DENSITY_XHIGH)
        suffix = "-xh"
    else if (metrics.densityDpi <= DisplayMetrics.DENSITY_XXHIGH || metrics.densityDpi > DisplayMetrics.DENSITY_XXHIGH)
        suffix = "-xxh"
    val pasvand = iconUrl.substring(iconUrl.lastIndexOf("."))
    val str = iconUrl.substring(0, iconUrl.lastIndexOf(".")) + suffix + pasvand
    return str
}

In order to test it, I need to mock Context and metrics.densityDpi to give it a value.
I'm using Mockk (1.9.3) library to do that.

@Test
fun getIconForDevice_ReturnsUrlWithXxhForXxhDisplay() {
    val context: Context = mockk(relaxed = true)
    every { context.resources.displayMetrics.densityDpi } returns 450
    assertEquals(IconHelper.getIconForDevice(context,
      "https://website.com/image.png"), "https://website.com/image-xxh.png")
}

Running the test following error stacktrace is given:

java.lang.ClassCastException: java.lang.Integer cannot be cast to android.util.DisplayMetrics

    at ...Resources.getDisplayMetrics(Resources.java)
    at ....IconHelper.getIconForDevice(IconHelper.kt:39)
    at ...IconHelperTest.getIconForDevice_ReturnsUrlWithXxhForXxhDisplay(IconHelperTest.kt:30)

The first link refers to val metrics = context.resources.displayMetrics line of the actual function

So how can I mock such a nested field in mockk?
context.resources.displayMetrics.densityDpi

Mahdi-Malv
  • 16,677
  • 10
  • 70
  • 117

2 Answers2

2

Your setup of mocking Context and densityDpi is correct.

The test fails because you are calling getIconForDevice with a new mockk instead of your mocked Context.

@Test
fun getIconForDevice_ReturnsUrlWithXxhForXxhDisplay() {
    val context: Context = mockk(relaxed = true)
    every { context.resources.displayMetrics.densityDpi } returns 450

    // pass in mocked context
    assertEquals(IconHelper.getIconForDevice(context, "https://website.com/image.png"), "https://website.com/image-xxh.png")
}

Update

Regarding your ClassCastException, because you are accessing the DisplayMetrics first:

val metrics = context.resources.displayMetrics

I think you need to separate the mock of displayMetrics and densityDpi:

@Test
fun getIconForDevice_ReturnsUrlWithXxhForXxhDisplay() {
  val context: Context = mockk(relaxed = true)
  val displayMetrics: DisplayMetrics = mockk(relaxed = true) // maybe relaxed is not needed, I just put it here in case
  every { context.resources.displayMetrics } returns displayMetrics
  every { displayMetrics.densityDpi } returns 450

  assertEquals(IconHelper.getIconForDevice(context, "https://website.com/image.png"), "https://website.com/image-xxh.png")
}
ChristianB
  • 2,452
  • 2
  • 10
  • 22
  • That was rookie mistake. Sorry. However, with the correction test fails with a cast exception. I'll update the question – Mahdi-Malv Feb 01 '21 at 08:01
  • @Mahdi-Malv no problem, can happen. I updated my answer regarding your `ClassCastException`. Hope it will fix your problem. – ChristianB Feb 01 '21 at 16:52
1

In version 1.12.0 Run as unit test kotlin 1.5.10

    // WORKS
    val context: Context = mockk()
    every { context.resources.displayMetrics } returns DisplayMetrics().apply {

        this.densityDpi = DisplayMetrics.DENSITY_LOW
    }

ERROR - returns class java.lang.Integer cannot be cast to class android.util.DisplayMetrics

    val context: Context = mockk()
    every { context.resources.displayMetrics.densityDpi } returns DisplayMetrics.DENSITY_LOW

ERROR - returns Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock

    val context: Context = mockk()
    val displayMetrics: DisplayMetrics = mockk()
    every { context.resources.displayMetrics } returns displayMetrics
    every { displayMetrics.densityDpi } returns DisplayMetrics.DENSITY_LOW
Dharman
  • 30,962
  • 25
  • 85
  • 135
Akhha8
  • 418
  • 3
  • 10
  • Getting the same ERROR - returns Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock – Mitch Thornton Mar 08 '23 at 22:57
  • @MitchThornton did you try the snipped in // WORKS ? – Akhha8 Mar 09 '23 at 19:14
  • @Ankhha8 It worked for mocking what you answered but I was trying to mock out something slightly different: TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, resources.displayMetrics).toInt() But was running into problems – Mitch Thornton Mar 09 '23 at 23:56