All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.careem.mockingbird.test.InvocationRecorder.kt Maven / Gradle / Ivy

Go to download

A Koltin multiplatform library that provides an easier way to mock and write unit tests for a multiplatform project

There is a newer version: 2.20.0
Show newest version
/**
 *
 * Copyright Careem, an Uber Technologies Inc. company
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.careem.mockingbird.test

import co.touchlab.stately.collections.ConcurrentMutableMap

internal class InvocationRecorder {

    private val recorder = ConcurrentMutableMap>()
    private val responses = ConcurrentMutableMap Any?>>()

    /**
     * This function must be called by the mock when a function call is executed on it
     * @param uuid the uuid of the mock
     * @param invocation the Invocation object @see [Invocation]
     */
    fun storeInvocation(uuid: String, invocation: Invocation) {
        recorder.block { map ->
            val list = map.getOrPut(uuid) { mutableListOf() }
            list.add(invocation)
        }
    }

    /**
     * This function returns the list of invocations registered for a certain mock
     * @param uuid the uuid of the mock
     * @return list of [Invocation]s object (all the methods calls with args)
     */
    internal fun getInvocations(uuid: String): List {
        return recorder[uuid] ?: emptyList()
    }

    /**
     * This function tells to InvocationRecorder how to reply if invocation is received
     * @param uuid the uuid of the mock
     * @param invocation the Invocation object @see [Invocation]
     * @param response the object that must be returned if the specif invocation happen
     */
    fun  storeResponse(uuid: String, invocation: Invocation, response: T) {
        val answer: (Invocation) -> T = { _ -> response }
        storeAnswer(uuid, invocation, answer)
    }

    /**
     * This function tells to InvocationRecorder how to reply if invocation is received
     * @param uuid the uuid of the mock
     * @param invocation the Invocation object @see [Invocation]
     * @param answer the lambda that must be invoked when the invocation happen
     */
    fun  storeAnswer(uuid: String, invocation: Invocation, answer: (Invocation) -> T) {
        responses.block { map ->
            val invocationsMap = map.getOrPut(uuid) { LinkedHashMap() }
            invocationsMap[invocation] = answer as (Invocation) -> Any?
        }
    }

    /**
     * This function will return the mocked response previously stored for the specific invocation
     * @param uuid the uuid of the mock
     * @param invocation the Invocation object @see [Invocation]
     * @param relaxed specify if we want to crash when no mock behavior provided
     * @throws IllegalStateException if no response was stored for the instance and invocation
     * @return the mocked response, or null if relaxed (throws if not relaxed)
     */
    fun getResponse(uuid: String, invocation: Invocation, relaxed: Boolean = false): Any? {
        return if (uuid in responses) {
            responses.getValue(uuid).let {
                val lambda = findResponseByInvocation(it, invocation, relaxed)
                return@let lambda(invocation)
            }
        } else if (relaxed) {
            null
        } else {
            throw IllegalStateException("Not mocked response for current object and instance, instance:$uuid, invocation: $invocation")
        }
    }

    /**
     * Helper to find stores response, need to take care of @see [AnyMatcher]
     */
    private fun findResponseByInvocation(
        storedInvocationMap: Map Any?>,
        invocation: Invocation,
        relaxed: Boolean
    ): ((Invocation) -> Any?) {
        for (storedInvocation in storedInvocationMap.keys.reversed()) {
            if (compareInvocation(storedInvocation, invocation)) {
                return storedInvocationMap[storedInvocation]!!
            }
        }
        if (relaxed) {
            return { null }
        } else {
            throw IllegalStateException("Not mocked response for current object and instance, invocation: $invocation")
        }
    }

    /**
     * Helper to compare invocation and any() matcher
     */
    private fun compareInvocation(
        storedInvocation: Invocation,
        actualInvocation: Invocation
    ): Boolean {
        if (storedInvocation.methodName != actualInvocation.methodName) {
            return false
        } else if (storedInvocation.arguments.size != actualInvocation.arguments.size) {
            return false
        }
        for (key in storedInvocation.arguments.keys) {
            if (!actualInvocation.arguments.containsKey(key)) {
                return false
            } else if (storedInvocation.arguments[key] !is AnyMatcher &&
                storedInvocation.arguments[key] != actualInvocation.arguments[key]
            ) {
                return false
            }
        }
        return true
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy