kosi-libs / MocKMP

A mocking processor for Kotlin Multiplatform
https://kosi-libs.org/mockmp
MIT License
183 stars 12 forks source link

Add mocking for suspend functions #59

Closed dalewking closed 9 months ago

dalewking commented 1 year ago

The mocking for functional types is great, but you seem to have left out mocking for suspend functions. I should be able to say

val callback: suspend (User) -> Unit = mockSuspendFunction1(mocker)
dalewking commented 1 year ago

I created function mocking support on top of Mockative and one of the useful features to have is a way to tell mock suspend function to block its return until you tell it to resume later. This is useful for testing for example that a loading status is true while a bit of code has not returned yet.

dalewking commented 1 year ago

Ignoring my comment about pausing (which i can handle in different ways) this should probably work:

@PublishedApi
internal class Anonymous()

public fun methodName(vararg paramTypes: String) =
    "invoke(${paramTypes.joinToString(", ")})"

public suspend fun <R> mockSuspendFunction0(
    mocker: Mocker,
    block: (suspend () -> R)? = null,
): suspend () -> R =
    Anonymous().let { rec ->
        suspend {
            mocker.registerSuspend<R>(rec, "invoke()")
        }
    }.also {
        if (block != null) mocker.everySuspending { it() } runs { block() }
    }

public suspend inline fun <R, reified A1>
mockSuspendFunction1(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1) -> R)? = null,
): suspend (A1) -> R {
    val instance = Anonymous()
    val function: suspend (A1) -> R =
        { a1: A1 ->
            mocker.registerSuspend(instance, methodName, a1)
        }

    return function.also {
        if (block != null) mocker.everySuspending { it(isAny()) } runs { block(it[0] as A1) }
    }
}

public suspend inline fun <R, reified A1, reified A2>
mockSuspendFunction2(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2) -> R)? = null,
): suspend (A1, A2) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2) -> R =
        { a1: A1, a2: A2 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3>
mockSuspendFunction3(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3) -> R)? = null,
): suspend (A1, A2, A3) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3) -> R =
        { a1: A1, a2: A2, a3: A3 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4>
mockSuspendFunction4(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3, A4) -> R)? = null,
): suspend (A1, A2, A3, A4) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3, A4) -> R =
        { a1: A1, a2: A2, a3: A3, a4: A4 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3, a4)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3, it[3] as A4) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5>
mockSuspendFunction5(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3, A4, A5) -> R)? = null,
): suspend (A1, A2, A3, A4, A5) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3, A4, A5) -> R =
        { a1: A1, a2: A2, a3: A3, a4: A4, a5: A5 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3, a4, a5)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3, it[3] as A4, it[4] as A5) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6>
mockSuspendFunction6(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3, A4, A5, A6) -> R =
        { a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3, a4, a5, a6)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny(), isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3, it[3] as A4, it[4] as A5, it[5] as A6) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7>
mockSuspendFunction7(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3, A4, A5, A6, A7) -> R =
        { a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3, a4, a5, a6, a7)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3, it[3] as A4, it[4] as A5, it[5] as A6, it[6] as A7) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7, reified A8>
mockSuspendFunction8(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7, A8) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7, A8) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3, A4, A5, A6, A7, A8) -> R =
        { a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3, a4, a5, a6, a7, a8)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3, it[3] as A4, it[4] as A5, it[5] as A6, it[6] as A7, it[7] as A8) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7, reified A8, reified A9>
mockSuspendFunction9(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9) -> R =
        { a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3, a4, a5, a6, a7, a8, a9)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3, it[3] as A4, it[4] as A5, it[5] as A6, it[6] as A7, it[7] as A8, it[8] as A9) }
    }
}

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7, reified A8, reified A9, reified A10>
mockSuspendFunction10(
    mocker: Mocker,
    methodName: String,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) -> R {
    val rec = Anonymous()
    val function: suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) -> R =
        { a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10 ->
            mocker.registerSuspend<R>(rec, methodName, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)
        }
    return function.also {
        if (block != null) mocker.everySuspending { it(isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny(), isAny()) } runs { block(it[0] as A1, it[1] as A2, it[2] as A3, it[3] as A4, it[4] as A5, it[5] as A6, it[6] as A7, it[7] as A8, it[8] as A9, it[9] as A10) }
    }
}

public suspend inline fun <R, reified A1>
mockSuspendFunction1(
    mocker: Mocker,
    noinline block: (suspend (A1) -> R)? = null,
): suspend (A1) -> R =
    mockSuspendFunction1(mocker, methodName(A1::class.bestName()), block)

public suspend inline fun <R, reified A1, reified A2>
mockSuspendFunction2(
    mocker: Mocker,
    noinline block: (suspend (A1, A2) -> R)? = null,
): suspend (A1, A2) -> R =
    mockSuspendFunction2(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3>
mockSuspendFunction3(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3) -> R)? = null,
): suspend (A1, A2, A3) -> R =
    mockSuspendFunction3(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4>
mockSuspendFunction4(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3, A4) -> R)? = null,
): suspend (A1, A2, A3, A4) -> R =
    mockSuspendFunction4(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName(), A4::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5>
mockSuspendFunction5(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3, A4, A5) -> R)? = null,
): suspend (A1, A2, A3, A4, A5) -> R =
    mockSuspendFunction5(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName(), A4::class.bestName(), A5::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6>
mockSuspendFunction6(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6) -> R =
    mockSuspendFunction6(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName(), A4::class.bestName(), A5::class.bestName(), A6::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7>
mockSuspendFunction7(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7) -> R =
    mockSuspendFunction7(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName(), A4::class.bestName(), A5::class.bestName(), A6::class.bestName(), A7::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7, reified A8>
mockSuspendFunction8(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7, A8) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7, A8) -> R =
    mockSuspendFunction8(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName(), A4::class.bestName(), A5::class.bestName(), A6::class.bestName(), A7::class.bestName(), A8::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7, reified A8, reified A9>
mockSuspendFunction9(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9) -> R =
    mockSuspendFunction9(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName(), A4::class.bestName(), A5::class.bestName(), A6::class.bestName(), A7::class.bestName(), A8::class.bestName(), A9::class.bestName()),
        block,
    )

public suspend inline fun <R, reified A1, reified A2, reified A3, reified A4, reified A5, reified A6, reified A7, reified A8, reified A9, reified A10>
mockSuspendFunction10(
    mocker: Mocker,
    noinline block: (suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) -> R)? = null,
): suspend (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) -> R =
    mockSuspendFunction10(
        mocker,
        methodName(A1::class.bestName(), A2::class.bestName(), A3::class.bestName(), A4::class.bestName(), A5::class.bestName(), A6::class.bestName(), A7::class.bestName(), A8::class.bestName(), A9::class.bestName(), A10::class.bestName()),
        block,
    )