Search code examples
kotlinjunitjunit5mockk

Debugging and the actual test result differs when mocking a object in Kotlin


I have written a testcase in which I mock a object using mockk. The problem is that when I run the testcase and debug using Intellij I do get the mocked response inside the debug window but when I let the testcase finish I get an error of my database client (so it ran the actual method instead).

The following is my simplified service code:

class SendMessageService (
    private val authHelper: AuthHelper
) {
    companion object {
        val targetsDb = TargetsDynamodbService()
    }

    fun handlePushIdRequest(sendMessageRequest: SendMessageRequest): Observable<List<Target>> {
        if (!validAuthorization(sendMessageRequest.serviceKey)) {
            throw ClientErrorException("Invalid Authorization", Response.Status.UNAUTHORIZED)
        }

        return targetsDb.getAllTargetsAsObservable(sendMessageRequest.pushId)
    }

    private fun validAuthorization(serviceKey: ServiceKey): Boolean {
        val authorization = authHelper.getTargetId("service key")

        return authorization == Configuration.SendMessageAuthorization.authorization(serviceKey)
    }
}

With this as simplified testcase:

class SendMessageServiceTest : AbstractServiceTest() {
    private val authHelper = AuthHelper(listOf("abc"))

    private val targetsDynamodbService: TargetsDynamodbService = mockk()
    private lateinit var service: SendMessageService

    @BeforeEach
    fun setUp() {
        mockkObject(SendMessageService.Companion)
        every { SendMessageService.Companion.targetsDb } returns targetsDynamodbService
        ConfigurationManager.getConfigInstance().setProperty("sendmessage.authorization.test", "abc")
    }

    @Test
    fun `successfully send pushId request message`() {
        every { targetsDynamodbService.getAllTargetsAsObservable(any()) } answers {
            Observable.just(listOf(Target().apply { pushId = arg<String>(0) }))
        }
        service = SendMessageService(authHelper)

        val response = service.handlePushIdRequests(createMessageRequest()).blockingFirst()
        assertTrue(response.isNotEmpty())
    }


    private fun createMessageRequest() =
        SendMessageRequest(
            pushId = "pushId",
            serviceKey = ServiceKey.TEST,
            title = TextLocalization("en", "nl"),
            body = TextLocalization("en", "nl"),
            silent = false,
            collapseKey = null,
            data = null,
            androidOptions = null,
            iosOptions = null
        )
}

What am I missing that a debug and the actual testcase result can differ?


Solution

  • every { ... } mocks a function, not a field. In your particular case Kotlin compiler doesn't generate getter and setter for a field because it's not required. So to solve your problem you have to force compiler to generate getter (which is a function) for the desired property, e.g. using by lazy:

    Trick #1

    companion object {
        val targetsDb by lazy { TargetsDynamodbService() }
    }
    

    ... or to declare getter explicitly:

    Trick #2

    companion object {
        private val _targetsDb = TargetsDynamodbService() // backing field
    
        val targetsDb: TargetsDynamodbService
            get() = _targetsDb     // get() is an explicit getter for _targetsDb 
    }
    

    P.S. However, I'd recommend you to review the design of your class, and consider about injecting targetsDb through constructor rather than instantiating it inside Companion object.