Search code examples
unit-testingkotlinmockkmockk-verify

mockk, how to use slot for MutableMap<String, String>


using mockk 1.9.3

having a function to be verified

class EventLogger private constructor()

fun logUserEvent(eventName: String?, eventParamMap: MutableMap<String, String>?) {
   ......
    internaLogEventImpl(eventName, eventParamMap)
}

internal fun internaLogEventImpl(eventName: String?, customParams: MutableMap<String, String>?) { ...... }

companion object {
     @Volatile
     private var sEventLoggerSingleton: EventLogger? = null

     @JvmStatic
    val instance: EventLogger
        get() {
            if (sEventLoggerSingleton == null) {
                sEventLoggerSingleton = EventLogger()
            }
            return sEventLoggerSingleton!!
        }
}

got compiler error at every {eventLogger.internaLogEventImpl(any(), mapSlot)} enter image description here

Type mismatch. Required: MutableMap<String, String>? Found: CapturingSlot<MutableMap<String, String>> when trying this below :

class TestK {

lateinit var eventLogger: EventLogger
lateinit var application: Application

val mapSlot = slot<MutableMap<String, String>>()

@Before
fun setUp() {
    application = ApplicationProvider.getApplicationContext<Application>()
    eventLogger = mockk.spyk(EventLogger.instance)
    ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", eventLogger)
}

@After
fun cleanUp() {
    ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", null)
}

@Test
fun logNotificationStatusChange_with_enabled_WhenCalled_ShouldLog() {

    val testMap = hashMapOf("action" to "open")

    every {eventLogger.internaLogEventImpl(any(), mapSlot)} answers {
        println(mapSlot.captured)
        assert(mapSlot.captured["action"] == "open")
    }
    eventLogger.logUserEvent("test_event", testMap)
}

}


Solution

  • You need to use capture (see Mockk's Capturing section).

    So for your case capture(mapSlot) should work.

    eventLogger.internaLogEventImpl(any(), capture(mapSlot))
    

    Before trying to mock on complex code, It would be better to learn on easier example.

    Here is a working example to mock a private call on an object with mockk.

    object MyLogger {
        fun logUserEvent(event: String?, map: MutableMap<String, String>?) {
            // turns the event string into an uppercase string.
            internaLogEventImpl(event?.toUpperCase(), map)
        }
    
        private fun internaLogEventImpl(event: String?, map: MutableMap<String, String>?): Unit =
            throw Exception("real implementation")
    }
    

    How to test and mock the internal function so we don't throw the exception.

    @Test
    fun `test logger internal`() {
        val expectedMap = mutableMapOf("a" to "b")
        val expectedEvent = "EVENT"
    
        val mock = spyk(MyLogger, recordPrivateCalls = true)
        justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
        // or justRun { mock["internaLogEventImpl"](any<String>(), any<MutableMap<String, String>>()) }
    
        mock.logUserEvent("event", expectedMap)
    
        verify { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
    }
    

    Here logUserEvent calls the real implementation and internaLogEventImpl calls the mock implementation.

    if justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) } is not called (or wrong because the argument doesn't match), the real implementation will be call. Here it will throw Exception("real implementation").

    Please try and modify values to check different behaviors.