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

io.mockk.impl.verify.UnorderedCallVerifier.kt Maven / Gradle / Ivy

package io.mockk.impl.verify

import io.mockk.InternalPlatformDsl.toStr
import io.mockk.Invocation
import io.mockk.InvocationMatcher
import io.mockk.MockKGateway.CallVerifier
import io.mockk.MockKGateway.VerificationResult
import io.mockk.RecordedCall
import io.mockk.impl.log.SafeLog
import io.mockk.impl.stub.StubRepository
import io.mockk.impl.verify.VerificationHelpers.formatCalls
import io.mockk.impl.verify.VerificationHelpers.stackTrace
import io.mockk.impl.verify.VerificationHelpers.stackTraces

open class UnorderedCallVerifier(
    val stubRepo: StubRepository,
    val safeLog: SafeLog
) : CallVerifier {
    private val captureBlocks = mutableListOf<() -> Unit>()

    override fun verify(verificationSequence: List, min: Int, max: Int): VerificationResult {
        for ((i, call) in verificationSequence.withIndex()) {
            val callIdxMsg = safeLog.exec { "call ${i + 1} of ${verificationSequence.size}: ${call.matcher}" }
            val result = matchCall(call, min, max, callIdxMsg)

            if (!result.matches) {
                return result
            }
        }
        return VerificationResult(true)
    }

    private fun matchCall(recordedCall: RecordedCall, min: Int, max: Int, callIdxMsg: String): VerificationResult {
        val stub = stubRepo.stubFor(recordedCall.matcher.self)
        val allCallsForMock = stub.allRecordedCalls()
        val allCallsForMockMethod = allCallsForMock.filter {
            recordedCall.matcher.method == it.method
        }


        val result = if (min == 0 && max == 0) {
            val n = allCallsForMockMethod.filter { recordedCall.matcher.match(it) }.count()
            if (n == 0) {
                VerificationResult(true)
            } else {
                VerificationResult(
                    false, "$callIdxMsg should not be called" +
                            "\n\nCalls:\n" +
                            formatCalls(allCallsForMockMethod) +
                            "\n\nStack traces:\n" +
                            stackTraces(allCallsForMockMethod)
                )
            }
        } else when (allCallsForMockMethod.size) {
            0 -> {
                if (min == 0 && max == 0) {
                    VerificationResult(true)
                } else if (allCallsForMock.isEmpty()) {
                    VerificationResult(false, "$callIdxMsg was not called")
                } else {
                    VerificationResult(false, safeLog.exec {
                        "$callIdxMsg was not called." +
                                "\n\nCalls to same mock:\n" +
                                formatCalls(allCallsForMock) +
                                "\n\nStack traces:\n" +
                                stackTraces(allCallsForMock)
                    })
                }
            }
            1 -> {
                val onlyCall = allCallsForMockMethod.get(0)
                if (recordedCall.matcher.match(onlyCall)) {
                    if (1 in min..max) {
                        VerificationResult(true)
                    } else {
                        VerificationResult(
                            false,
                            "$callIdxMsg. One matching call found, but needs at least $min${atMostMsg(max)} calls" +
                                    "\nCall: " + allCallsForMock.first() +
                                    "\nStack trace:\n" +
                                    stackTrace(0, allCallsForMock.first().callStack)

                        )
                    }
                } else {
                    VerificationResult(false, safeLog.exec {
                        "$callIdxMsg. Only one matching call to ${stub.toStr()}/${recordedCall.matcher.method.toStr()} happened, but arguments are not matching:\n" +
                                describeArgumentDifference(recordedCall.matcher, onlyCall) +
                                "\nStack trace:\n" +
                                stackTrace(0, allCallsForMock.first().callStack)
                    })
                }
            }
            else -> {
                val n = allCallsForMockMethod.filter { recordedCall.matcher.match(it) }.count()
                if (n in min..max) {
                    VerificationResult(true)
                } else {
                    if (n == 0) {
                        VerificationResult(false,
                            safeLog.exec {
                                "$callIdxMsg. No matching calls found." +
                                        "\n\nCalls to same method:\n" +
                                        formatCalls(allCallsForMockMethod) +
                                        "\n\nStack traces:\n" +
                                        stackTraces(allCallsForMockMethod)
                            })
                    } else {
                        VerificationResult(
                            false,
                            "$callIdxMsg. $n matching calls found, " +
                                    "but needs at least $min${atMostMsg(max)} calls" +
                                    "\nCalls:\n" +
                                    formatCalls(allCallsForMock) +
                                    "\n\nStack traces:\n" +
                                    stackTraces(allCallsForMock)
                        )
                    }
                }
            }
        }

        captureBlocks.add({
            for (call in allCallsForMockMethod) {
                recordedCall.matcher.captureAnswer(call)
            }
        })

        return result
    }

    override fun captureArguments() {
        captureBlocks.forEach { it() }
    }

    private fun atMostMsg(max: Int) = if (max == Int.MAX_VALUE) "" else " and at most $max"

    private fun describeArgumentDifference(
        matcher: InvocationMatcher,
        invocation: Invocation
    ): String {
        val str = StringBuilder()
        for ((i, arg) in invocation.args.withIndex()) {
            val argMatcher = matcher.args[i]
            val matches = argMatcher.match(arg)
            str.append("[$i]: argument: $arg, matcher: $argMatcher, result: ${if (matches) "+" else "-"}\n")
        }
        return str.toString()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy