diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index d93c45636..fed95bd2c 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -16,8 +16,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java-version: [ 17, 19 ] # test LTS versions, and the newest - kotlin-version: [ 1.5.31, 1.6.21, 1.7.22, 1.8.20, 1.9.10 ] + java-version: [ 17, 19, 21 ] # test LTS versions, and the newest + kotlin-version: [ 1.5.31, 1.6.21, 1.7.22, 1.8.20, 1.9.10, 2.0.0 ] fail-fast: false # in case one JDK fails, we still want to see results from others name: "[java=${{ matrix.java-version }}, kotlin=${{ matrix.kotlin-version }}]" timeout-minutes: 30 @@ -31,7 +31,7 @@ jobs: java-version: ${{ matrix.java-version }} - name: Setup Gradle - uses: gradle/gradle-build-action@v2.4.2 + uses: gradle/actions/setup-gradle@v4 - name: Run tests with Gradle run: > @@ -41,10 +41,10 @@ jobs: -Pio_mockk_java_toolchain_test_version=${{ matrix.java-version }} android-instrumented-tests: - runs-on: macos-latest + runs-on: macos-12 strategy: matrix: - api-level: [ 29 ] # Relevant SDK versions to test for MockK Android projects + api-level: [ 30 ] # Relevant SDK versions to test for MockK Android projects fail-fast: false # in case one API-level fails, we still want to see results from others name: "[api-level=${{ matrix.api-level }}]" timeout-minutes: 30 @@ -57,7 +57,10 @@ jobs: java-version: 17 - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v4 + + - name: Set AVD Architecture Based on API Level + run: echo "ARCH=$(if [ ${{ matrix.api-level }} -eq 27 ]; then echo 'x86'; else echo 'x86_64'; fi)" >> $GITHUB_ENV - name: AVD cache uses: actions/cache@v3 @@ -72,14 +75,18 @@ jobs: if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: + arch: ${{ env.ARCH }} api-level: ${{ matrix.api-level }} force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false script: echo "Generated AVD snapshot for caching." + emulator-build: 7425822 # workaround for emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160 - name: run tests uses: reactivecircus/android-emulator-runner@v2 with: + arch: ${{ env.ARCH }} api-level: ${{ matrix.api-level }} script: ./gradlew connectedCheck + emulator-build: 7425822 # workaround for emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b72dc75c0..7b6465be8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,8 +11,10 @@ Please note we have a code of conduct, please follow it in all your interactions 1. Make sure all tests pass and you have a passing build for the whole project (run `./gradlew check`). 2. Whether you are fixing a bug or introducing a new feature, please add the necessary tests. - It's fine to create a test named "Issue`number`Test", where `number` is the number of the GitHub issue your test - is reproducing, but if you can make the extra effort to add your test coverage in an existing test group that would be great <3 + Please avoid creating tests named "Issue`number`Test", where `number` is the number of the GitHub issue your test + is reproducing; these require users to go to GitHub to understand what the code is doing. If you can, make the extra + effort to add your test coverage in an existing test group, or create a new test class that clearly describes the + behavior you are testing. 3. Detail the changes you are introducing in the pull request description. 4. Remember that, at least for the time being, we don't have a full-time maintainer for the project, so it may still take some time :) diff --git a/README.md b/README.md index 29847127c..f15ea0472 100644 --- a/README.md +++ b/README.md @@ -263,9 +263,10 @@ each test class execution. You can disable this behavior by adding the `@MockKExtension.KeepMocks` annotation to your class or globally by setting the `mockk.junit.extension.keepmocks=true` property. (Since v1.13.11) -Alternatively, since `clearAllMocks` is not thread-safe, if you need to run test in parallel you can add the +Alternatively, since `clearAllMocks` by default (`currentThreadOnly=false`) is not thread-safe, if you need to run test in parallel you can add the `MockKExtension.RequireParallelTesting` annotation to your class or set the `mockk.junit.extension.requireParallelTesting=true` property to disable calling it in the `@AfterAll` callback. +If `clearAllMocks` is explicitly called, you can supply `clearAllMocks(currentThreadOnly = true)` so that it only clears mocks created within the same thread (since v1.13.12). #### Automatic verification confirmation diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index c4cb6958c..15dd92c4b 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } // set the versions of Gradle plugins that the subprojects will use here -val kotlinPluginVersion: String = "1.9.10" +val kotlinPluginVersion: String = "2.0.0" val androidGradle = "8.1.1" val kotlinxKover = "0.7.3" diff --git a/buildSrc/src/main/kotlin/buildsrc/config/Deps.kt b/buildSrc/src/main/kotlin/buildsrc/config/Deps.kt index 700102cc2..fa7d89f6d 100644 --- a/buildSrc/src/main/kotlin/buildsrc/config/Deps.kt +++ b/buildSrc/src/main/kotlin/buildsrc/config/Deps.kt @@ -12,14 +12,14 @@ object Deps { val jvmTarget = JavaVersion.VERSION_1_8 const val dokka = "1.9.0" - const val kotlinDefault = "1.9.10" + const val kotlinDefault = "2.0.0" const val coroutines = "1.6.4" const val slfj = "2.0.5" const val logback = "1.4.5" const val junitJupiter = "5.8.2" const val junit4 = "4.13.2" - const val byteBuddy = "1.14.6" + const val byteBuddy = "1.14.17" const val objenesis = "3.3" const val dexmaker = "2.28.3" const val androidxEspresso = "3.5.1" diff --git a/gradle.properties b/gradle.properties index 089e0e825..99dfc0c4c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=1.13.11-SNAPSHOT +version=1.13.14-SNAPSHOT # Enable Gradle build cache https://docs.gradle.org/current/userguide/build_cache.html org.gradle.caching=true org.gradle.configureondemand=false @@ -10,9 +10,9 @@ kotlin.mpp.stability.nowarn=true localrepo=build/maven-local-repo ######################## # version of Java that will be used to build the project -io_mockk_java_toolchain_main_version=11 +io_mockk_java_toolchain_main_version=17 # the Java version tests will run against - this is overridden in the GitHub actions -io_mockk_java_toolchain_test_version=11 +io_mockk_java_toolchain_test_version=17 ######################## # Android properties: android.useAndroidX=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c4..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f862f..0aaefbcaf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a5..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -145,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +205,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/modules/mockk-agent-android/src/main/jni/mockkjvmtiagent/proxy-agent.cc b/modules/mockk-agent-android/src/main/jni/mockkjvmtiagent/proxy-agent.cc index 3eb0262e6..7383f22b9 100644 --- a/modules/mockk-agent-android/src/main/jni/mockkjvmtiagent/proxy-agent.cc +++ b/modules/mockk-agent-android/src/main/jni/mockkjvmtiagent/proxy-agent.cc @@ -355,7 +355,8 @@ namespace io_mockk_proxy_android { static bool canBeTransformedConstructor(ir::EncodedMethod *method) { - if ((method->access_flags & kAccConstructor) != 0) { + if ((method->access_flags & kAccConstructor) != 0 + && (method->access_flags & kAccStatic) == 0) { return true; } diff --git a/modules/mockk-android/src/androidTest/java/io/mockk/MockConstructorTest.kt b/modules/mockk-android/src/androidTest/java/io/mockk/MockConstructorTest.kt new file mode 100644 index 000000000..a969217c6 --- /dev/null +++ b/modules/mockk-android/src/androidTest/java/io/mockk/MockConstructorTest.kt @@ -0,0 +1,23 @@ +package io.mockk + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertEquals + +@RunWith(AndroidJUnit4::class) +class MockConstructorTest { + class MockCls { + fun add(a: Int, b: Int) = a + b + companion object { + const val VALUE = 5 + } + } + + @Test + fun testMockkConstructorClassWithCompanion() { + mockkConstructor(MockCls::class) + every { anyConstructed().add(1, 2) } returns 4 + assertEquals(4, MockCls().add(1, 2)) + } +} \ No newline at end of file diff --git a/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt b/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt index 393d72a65..b51c8e590 100644 --- a/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt +++ b/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt @@ -31,7 +31,13 @@ actual object ValueClassSupport { // method.returnType == int (the actual representation of inlined property on JVM) // method.kotlinFunction.returnType.classifier == Foo val expectedReturnType = kFunction.returnType.classifier - return if (resultType == expectedReturnType) { + val isReturnNullable = kFunction.returnType.isMarkedNullable + val isPrimitive = resultType.boxedClass.java.isPrimitive + return if ( + !(kFunction.isSuspend && isPrimitive) && + resultType == expectedReturnType && + !isReturnNullable + ) { this.boxedValue } else { this @@ -44,7 +50,8 @@ actual object ValueClassSupport { return this } else { val expectedReturnType = kProperty.returnType.classifier - return if (resultType == expectedReturnType) { + val isReturnNullable = kProperty.returnType.isMarkedNullable + return if (resultType == expectedReturnType && !isReturnNullable) { this.boxedValue } else { this diff --git a/modules/mockk-dsl/api/mockk-dsl.api b/modules/mockk-dsl/api/mockk-dsl.api index b06e4ff7a..50da55ff7 100644 --- a/modules/mockk-dsl/api/mockk-dsl.api +++ b/modules/mockk-dsl/api/mockk-dsl.api @@ -578,8 +578,8 @@ public final class io/mockk/MockKDsl { public static final field INSTANCE Lio/mockk/MockKDsl; public final fun internalCheckExactlyAtMostAtLeast (IIILio/mockk/Ordering;)V public final fun internalCheckUnnecessaryStub ([Ljava/lang/Object;)V - public final fun internalClearAllMocks (ZZZZZZZZZ)V - public static synthetic fun internalClearAllMocks$default (Lio/mockk/MockKDsl;ZZZZZZZZZILjava/lang/Object;)V + public final fun internalClearAllMocks (ZZZZZZZZZZ)V + public static synthetic fun internalClearAllMocks$default (Lio/mockk/MockKDsl;ZZZZZZZZZZILjava/lang/Object;)V public final fun internalClearConstructorMockk ([Lkotlin/reflect/KClass;ZZZZZ)V public static synthetic fun internalClearConstructorMockk$default (Lio/mockk/MockKDsl;[Lkotlin/reflect/KClass;ZZZZZILjava/lang/Object;)V public final fun internalClearMocks (Ljava/lang/Object;[Ljava/lang/Object;ZZZZZ)V @@ -709,7 +709,7 @@ public final class io/mockk/MockKGateway$ClearOptions { public abstract interface class io/mockk/MockKGateway$Clearer { public abstract fun clear ([Ljava/lang/Object;Lio/mockk/MockKGateway$ClearOptions;)V - public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V } public final class io/mockk/MockKGateway$Companion { @@ -720,7 +720,7 @@ public final class io/mockk/MockKGateway$Companion { public abstract interface class io/mockk/MockKGateway$ConstructorMockFactory { public abstract fun clear (Lkotlin/reflect/KClass;Lio/mockk/MockKGateway$ClearOptions;)V - public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V public abstract fun constructorMockk (Lkotlin/reflect/KClass;ZZ)Lkotlin/jvm/functions/Function0; public abstract fun mockPlaceholder (Lkotlin/reflect/KClass;[Lio/mockk/Matcher;)Ljava/lang/Object; } @@ -774,13 +774,13 @@ public abstract interface class io/mockk/MockKGateway$MockTypeChecker { public abstract interface class io/mockk/MockKGateway$ObjectMockFactory { public abstract fun clear (Ljava/lang/Object;Lio/mockk/MockKGateway$ClearOptions;)V - public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V public abstract fun objectMockk (Ljava/lang/Object;Z)Lkotlin/jvm/functions/Function0; } public abstract interface class io/mockk/MockKGateway$StaticMockFactory { public abstract fun clear (Lkotlin/reflect/KClass;Lio/mockk/MockKGateway$ClearOptions;)V - public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public abstract fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V public abstract fun staticMockk (Lkotlin/reflect/KClass;)Lkotlin/jvm/functions/Function0; } diff --git a/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt b/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt index 7bf260dee..1557ce58d 100644 --- a/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt +++ b/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/API.kt @@ -593,7 +593,8 @@ object MockKDsl { staticMocks: Boolean = true, constructorMocks: Boolean = true, verificationMarks: Boolean = true, - exclusionRules: Boolean = true + exclusionRules: Boolean = true, + currentThreadOnly: Boolean = false ) { val options = MockKGateway.ClearOptions( answers, @@ -605,16 +606,16 @@ object MockKDsl { val implementation = MockKGateway.implementation() if (regularMocks) { - implementation.clearer.clearAll(options) + implementation.clearer.clearAll(options, currentThreadOnly) } if (objectMocks) { - implementation.objectMockFactory.clearAll(options) + implementation.objectMockFactory.clearAll(options, currentThreadOnly) } if (staticMocks) { - implementation.staticMockFactory.clearAll(options) + implementation.staticMockFactory.clearAll(options, currentThreadOnly) } if (constructorMocks) { - implementation.constructorMockFactory.clearAll(options) + implementation.constructorMockFactory.clearAll(options, currentThreadOnly) } } @@ -748,6 +749,11 @@ open class MockKMatcherScope( */ inline fun any(): T = match(ConstantMatcher(true)) + /** + * Matches any nullable argument. + */ + inline fun anyNullable(): T = matchNullable { true } + inline fun capture(lst: MutableList): T = match(CaptureMatcher(lst, T::class)) /** diff --git a/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/GatewayAPI.kt b/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/GatewayAPI.kt index c41e99e21..e718ee6c0 100644 --- a/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/GatewayAPI.kt +++ b/modules/mockk-dsl/src/commonMain/kotlin/io/mockk/GatewayAPI.kt @@ -68,7 +68,7 @@ interface MockKGateway { fun clear(type: KClass<*>, options: ClearOptions) - fun clearAll(options: ClearOptions) + fun clearAll(options: ClearOptions, currentThreadOnly: Boolean) } /** @@ -79,7 +79,7 @@ interface MockKGateway { fun clear(obj: Any, options: ClearOptions) - fun clearAll(options: ClearOptions) + fun clearAll(options: ClearOptions, currentThreadOnly: Boolean) } /** @@ -96,7 +96,7 @@ interface MockKGateway { fun clear(type: KClass<*>, options: ClearOptions) - fun clearAll(options: ClearOptions) + fun clearAll(options: ClearOptions, currentThreadOnly: Boolean) } /** @@ -109,7 +109,8 @@ interface MockKGateway { ) fun clearAll( - options: ClearOptions + options: ClearOptions, + currentThreadOnly: Boolean ) } diff --git a/modules/mockk/api/mockk.api b/modules/mockk/api/mockk.api index 51926ce86..2faf80ffe 100644 --- a/modules/mockk/api/mockk.api +++ b/modules/mockk/api/mockk.api @@ -23,8 +23,8 @@ public final class io/mockk/MockKAnnotations { public final class io/mockk/MockKKt { public static final fun checkUnnecessaryStub ([Ljava/lang/Object;)V - public static final fun clearAllMocks (ZZZZZZZ)V - public static synthetic fun clearAllMocks$default (ZZZZZZZILjava/lang/Object;)V + public static final fun clearAllMocks (ZZZZZZZZ)V + public static synthetic fun clearAllMocks$default (ZZZZZZZZILjava/lang/Object;)V public static final fun clearConstructorMockk ([Lkotlin/reflect/KClass;ZZZ)V public static synthetic fun clearConstructorMockk$default ([Lkotlin/reflect/KClass;ZZZILjava/lang/Object;)V public static final fun clearMocks (Ljava/lang/Object;[Ljava/lang/Object;ZZZZZ)V @@ -329,7 +329,7 @@ public class io/mockk/impl/instantiation/JvmAnyValueGenerator : io/mockk/impl/in public final class io/mockk/impl/instantiation/JvmConstructorMockFactory : io/mockk/MockKGateway$ConstructorMockFactory { public fun (Lio/mockk/proxy/MockKConstructorProxyMaker;Lio/mockk/impl/stub/CommonClearer;Lio/mockk/impl/instantiation/AbstractMockFactory;Lio/mockk/proxy/MockKProxyMaker;Lio/mockk/impl/stub/StubGatewayAccess;)V public fun clear (Lkotlin/reflect/KClass;Lio/mockk/MockKGateway$ClearOptions;)V - public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V public fun constructorMockk (Lkotlin/reflect/KClass;ZZ)Lkotlin/jvm/functions/Function0; public final fun getClearer ()Lio/mockk/impl/stub/CommonClearer; public final fun getConstructorProxyMaker ()Lio/mockk/proxy/MockKConstructorProxyMaker; @@ -406,7 +406,7 @@ public final class io/mockk/impl/instantiation/JvmObjectMockFactory : io/mockk/M public static final field Companion Lio/mockk/impl/instantiation/JvmObjectMockFactory$Companion; public fun (Lio/mockk/proxy/MockKProxyMaker;Lio/mockk/impl/stub/StubRepository;Lio/mockk/impl/stub/StubGatewayAccess;)V public fun clear (Ljava/lang/Object;Lio/mockk/MockKGateway$ClearOptions;)V - public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V public final fun getGatewayAccess ()Lio/mockk/impl/stub/StubGatewayAccess; public final fun getProxyMaker ()Lio/mockk/proxy/MockKProxyMaker; public final fun getRefCntMap ()Lio/mockk/impl/instantiation/RefCounterMap; @@ -422,7 +422,7 @@ public final class io/mockk/impl/instantiation/JvmStaticMockFactory : io/mockk/M public static final field Companion Lio/mockk/impl/instantiation/JvmStaticMockFactory$Companion; public fun (Lio/mockk/proxy/MockKStaticProxyMaker;Lio/mockk/impl/stub/StubRepository;Lio/mockk/impl/stub/StubGatewayAccess;)V public fun clear (Lkotlin/reflect/KClass;Lio/mockk/MockKGateway$ClearOptions;)V - public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V public final fun getGatewayAccess ()Lio/mockk/impl/stub/StubGatewayAccess; public final fun getProxyMaker ()Lio/mockk/proxy/MockKStaticProxyMaker; public final fun getRefCntMap ()Lio/mockk/impl/instantiation/RefCounterMap; @@ -864,17 +864,12 @@ public abstract class io/mockk/impl/recording/states/CallRecordingState { } public final class io/mockk/impl/recording/states/ExclusionState : io/mockk/impl/recording/states/RecordingState { - public static final field Companion Lio/mockk/impl/recording/states/ExclusionState$Companion; public fun (Lio/mockk/impl/recording/CommonCallRecorder;Lio/mockk/MockKGateway$ExclusionParameters;)V public final fun getParams ()Lio/mockk/MockKGateway$ExclusionParameters; public fun recordingDone ()Lio/mockk/impl/recording/states/CallRecordingState; public fun wasNotCalled (Ljava/util/List;)V } -public final class io/mockk/impl/recording/states/ExclusionState$Companion { - public final fun getLog ()Lio/mockk/impl/log/Logger; -} - public abstract class io/mockk/impl/recording/states/RecordingState : io/mockk/impl/recording/states/CallRecordingState { public fun (Lio/mockk/impl/recording/CommonCallRecorder;)V protected final fun addWasNotCalled (Ljava/util/List;)V @@ -928,7 +923,7 @@ public final class io/mockk/impl/stub/AnswerAnsweringOpportunity : io/mockk/Answ public final class io/mockk/impl/stub/CommonClearer : io/mockk/MockKGateway$Clearer { public fun (Lio/mockk/impl/stub/StubRepository;Lio/mockk/impl/log/SafeToString;)V public fun clear ([Ljava/lang/Object;Lio/mockk/MockKGateway$ClearOptions;)V - public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;)V + public fun clearAll (Lio/mockk/MockKGateway$ClearOptions;Z)V public final fun getLog ()Lio/mockk/impl/log/Logger; public final fun getSafeToString ()Lio/mockk/impl/log/SafeToString; public final fun getStubRepository ()Lio/mockk/impl/stub/StubRepository; @@ -949,6 +944,7 @@ public final class io/mockk/impl/stub/ConstructorStub : io/mockk/impl/stub/Stub public final fun getRecordPrivateCalls ()Z public final fun getRepresentativeMock ()Ljava/lang/Object; public final fun getStub ()Lio/mockk/impl/stub/Stub; + public fun getThreadId ()J public fun getType ()Lkotlin/reflect/KClass; public fun handleInvocation (Ljava/lang/Object;Lio/mockk/MethodDescription;Lkotlin/jvm/functions/Function0;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; public fun markCallVerified (Lio/mockk/Invocation;)V @@ -983,6 +979,7 @@ public class io/mockk/impl/stub/MockKStub : io/mockk/impl/stub/Stub { public final fun getRecordPrivateCalls ()Z public final fun getRelaxUnitFun ()Z public final fun getRelaxed ()Z + public fun getThreadId ()J public fun getType ()Lkotlin/reflect/KClass; public fun handleInvocation (Ljava/lang/Object;Lio/mockk/MethodDescription;Lkotlin/jvm/functions/Function0;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; public fun markCallVerified (Lio/mockk/Invocation;)V @@ -1024,6 +1021,7 @@ public abstract interface class io/mockk/impl/stub/Stub : io/mockk/impl/platform public abstract fun clear (Lio/mockk/MockKGateway$ClearOptions;)V public abstract fun excludeRecordedCalls (Lio/mockk/MockKGateway$ExclusionParameters;Lio/mockk/InvocationMatcher;)V public abstract fun getName ()Ljava/lang/String; + public abstract fun getThreadId ()J public abstract fun getType ()Lkotlin/reflect/KClass; public abstract fun handleInvocation (Ljava/lang/Object;Lio/mockk/MethodDescription;Lkotlin/jvm/functions/Function0;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; public abstract fun markCallVerified (Lio/mockk/Invocation;)V @@ -1127,7 +1125,7 @@ public final class io/mockk/junit4/MockKRule : org/junit/rules/TestWatcher, org/ public fun (Ljava/lang/Object;)V } -public final class io/mockk/junit5/MockKExtension : org/junit/jupiter/api/extension/AfterAllCallback, org/junit/jupiter/api/extension/ParameterResolver, org/junit/jupiter/api/extension/TestInstancePostProcessor { +public final class io/mockk/junit5/MockKExtension : org/junit/jupiter/api/extension/AfterAllCallback, org/junit/jupiter/api/extension/AfterEachCallback, org/junit/jupiter/api/extension/ParameterResolver, org/junit/jupiter/api/extension/TestInstancePostProcessor { public static final field CHECK_UNNECESSARY_STUB_PROPERTY Ljava/lang/String; public static final field CONFIRM_VERIFICATION_PROPERTY Ljava/lang/String; public static final field Companion Lio/mockk/junit5/MockKExtension$Companion; @@ -1135,6 +1133,7 @@ public final class io/mockk/junit5/MockKExtension : org/junit/jupiter/api/extens public static final field REQUIRE_PARALLEL_TESTING Ljava/lang/String; public fun ()V public fun afterAll (Lorg/junit/jupiter/api/extension/ExtensionContext;)V + public fun afterEach (Lorg/junit/jupiter/api/extension/ExtensionContext;)V public fun postProcessTestInstance (Ljava/lang/Object;Lorg/junit/jupiter/api/extension/ExtensionContext;)V public fun resolveParameter (Lorg/junit/jupiter/api/extension/ParameterContext;Lorg/junit/jupiter/api/extension/ExtensionContext;)Ljava/lang/Object; public fun supportsParameter (Lorg/junit/jupiter/api/extension/ParameterContext;Lorg/junit/jupiter/api/extension/ExtensionContext;)Z diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt index bd98c2fa6..3e5c86c66 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/MockK.kt @@ -695,7 +695,8 @@ inline fun clearAllMocks( regularMocks: Boolean = true, objectMocks: Boolean = true, staticMocks: Boolean = true, - constructorMocks: Boolean = true + constructorMocks: Boolean = true, + currentThreadOnly: Boolean = false ) = MockK.useImpl { MockKDsl.internalClearAllMocks( answers, @@ -704,7 +705,8 @@ inline fun clearAllMocks( regularMocks, objectMocks, staticMocks, - constructorMocks + constructorMocks, + currentThreadOnly=currentThreadOnly ) } diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/instantiation/CommonMockTypeChecker.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/instantiation/CommonMockTypeChecker.kt index dfd6a9765..6ccc171fc 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/instantiation/CommonMockTypeChecker.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/instantiation/CommonMockTypeChecker.kt @@ -18,28 +18,17 @@ open class CommonMockTypeChecker( return stub !is SpyKStub<*> } - override fun isSpy(mock: Any): Boolean { + override fun isSpy(mock: Any): Boolean = isOfMockType(mock, MockType.SPY) + override fun isObjectMock(mock: Any): Boolean = isOfMockType(mock, MockType.OBJECT) + override fun isStaticMock(mock: Any): Boolean = isOfMockType(mock, MockType.STATIC) - val stub = stubRepository[mock] as? SpyKStub<*> - ?: return false - - return stub.mockType == MockType.SPY - } - - override fun isObjectMock(mock: Any): Boolean { - val stub = stubRepository[mock] as? SpyKStub<*> - ?: return false - - return stub.mockType == MockType.OBJECT - } + override fun isConstructorMock(mock: Any) = + if (mock is KClass<*>) isConstructorMockFun(mock) else false - override fun isStaticMock(mock: Any): Boolean { + private fun isOfMockType(mock: Any, mockType: MockType): Boolean { val stub = stubRepository[mock] as? SpyKStub<*> ?: return false - return stub.mockType == MockType.STATIC + return stub.mockType == mockType } - - override fun isConstructorMock(mock: Any) = - if (mock is KClass<*>) isConstructorMockFun(mock) else false } diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/platform/CommonIdentityHashMapOf.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/platform/CommonIdentityHashMapOf.kt index 91a45f581..284b77eb4 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/platform/CommonIdentityHashMapOf.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/platform/CommonIdentityHashMapOf.kt @@ -26,9 +26,7 @@ class CommonIdentityHashMapOf : MutableMap { override val values: MutableCollection get() = map.values - override fun clear() { - map.clear() - } + override fun clear() = map.clear() override fun put(key: K, value: V): V? = map.put(ref(key), value) diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt index 5e4db3de4..c8a1065aa 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt @@ -155,12 +155,11 @@ class ChainedCallDetector(safeToString: SafeToString) { } companion object { - fun eqOrNullMatcher(arg: Any?): Matcher { - return if (arg == null) { + fun eqOrNullMatcher(arg: Any?): Matcher = + if (arg == null) { NullCheckMatcher(false) } else { EqMatcher(arg) } - } } } diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/ExclusionState.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/ExclusionState.kt index 1f0f6fa8a..bab95fdd9 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/ExclusionState.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/ExclusionState.kt @@ -2,7 +2,6 @@ package io.mockk.impl.recording.states import io.mockk.MockKException import io.mockk.MockKGateway.ExclusionParameters -import io.mockk.impl.log.Logger import io.mockk.impl.recording.CommonCallRecorder class ExclusionState( @@ -34,7 +33,4 @@ class ExclusionState( } } - companion object { - val log = Logger() - } } \ No newline at end of file diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/VerifyingState.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/VerifyingState.kt index b251833d7..061411cb0 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/VerifyingState.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/recording/states/VerifyingState.kt @@ -13,9 +13,7 @@ class VerifyingState( val params: VerificationParameters ) : RecordingState(recorder) { - override fun wasNotCalled(list: List) { - addWasNotCalled(list) - } + override fun wasNotCalled(list: List) = addWasNotCalled(list) override fun recordingDone(): CallRecordingState { checkMissingCalls() diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/CommonClearer.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/CommonClearer.kt index 8f67e675d..0a828b5f0 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/CommonClearer.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/CommonClearer.kt @@ -18,8 +18,12 @@ class CommonClearer( } } - override fun clearAll(options: MockKGateway.ClearOptions) { + override fun clearAll(options: MockKGateway.ClearOptions, currentThreadOnly: Boolean) { + val currentThreadId = Thread.currentThread().id stubRepository.allStubs.forEach { + if (currentThreadOnly && currentThreadId != it.threadId) { + return@forEach + } it.clear(options) } } diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/ConstructorStub.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/ConstructorStub.kt index d17fb9ed3..e6bfc1823 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/ConstructorStub.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/ConstructorStub.kt @@ -24,6 +24,9 @@ class ConstructorStub( override val type: KClass<*> get() = stub.type + override val threadId: Long + get() = stub.threadId + override fun addAnswer(matcher: InvocationMatcher, answer: Answer<*>) = stub.addAnswer(matcher.substitute(represent), answer) diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/MockKStub.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/MockKStub.kt index ecdfe065b..3e5404ee5 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/MockKStub.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/MockKStub.kt @@ -17,6 +17,8 @@ open class MockKStub( ) : Stub { val log = gatewayAccess.safeToString(Logger()) + override val threadId: Long = Thread.currentThread().id + private val answers = InternalPlatform.synchronizedMutableList() private val childs = InternalPlatform.synchronizedMutableMap() private val recordedCalls = InternalPlatform.synchronizedMutableList() diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/Stub.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/Stub.kt index 46572434d..c46b5a8a1 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/Stub.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/stub/Stub.kt @@ -10,6 +10,8 @@ interface Stub : Disposable { val type: KClass<*> + val threadId: Long + fun addAnswer(matcher: InvocationMatcher, answer: Answer<*>) fun answer(invocation: Invocation): Any? diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/OrderedCallVerifier.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/OrderedCallVerifier.kt index d8adadb59..5b80e63c8 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/OrderedCallVerifier.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/OrderedCallVerifier.kt @@ -41,8 +41,6 @@ class OrderedCallVerifier( } - override fun captureArguments() { - captureBlocks.forEach { it() } - } + override fun captureArguments() = captureBlocks.forEach { it() } } diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/SequenceCallVerifier.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/SequenceCallVerifier.kt index c0b882802..6c7605d4a 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/SequenceCallVerifier.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/SequenceCallVerifier.kt @@ -46,7 +46,5 @@ class SequenceCallVerifier( return VerificationResult.OK(allCalls) } - override fun captureArguments() { - captureBlocks.forEach { it() } - } -} \ No newline at end of file + override fun captureArguments() = captureBlocks.forEach { it() } +} diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/TimeoutVerifier.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/TimeoutVerifier.kt index 0934820e9..317dff731 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/TimeoutVerifier.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/TimeoutVerifier.kt @@ -36,9 +36,7 @@ class TimeoutVerifier( } } - override fun captureArguments() { - verifierChain.captureArguments() - } + override fun captureArguments() = verifierChain.captureArguments() private fun List.allStubs(stubRepo: StubRepository) = this.map { InternalPlatform.ref(it.matcher.self) } diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/UnorderedCallVerifier.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/UnorderedCallVerifier.kt index b0f7f026a..91d894a34 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/UnorderedCallVerifier.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/UnorderedCallVerifier.kt @@ -58,7 +58,7 @@ open class UnorderedCallVerifier( val matchedCalls = allCallsForMockMethod.filter(matcher::match) - if(matchedCalls.size > 1 && matcher.args.any { it is CapturingSlotMatcher<*> }) { + if (matchedCalls.size > 1 && matcher.args.any { it is CapturingSlotMatcher<*> }) { val msg = "$matcher execution is being verified more than once and its arguments are being captured with a slot.\n" + "This will store only the argument of the last invocation in the slot.\n" + "If you want to store all the arguments, use a mutableList to capture arguments." @@ -82,7 +82,7 @@ open class UnorderedCallVerifier( } } else when (allCallsForMockMethod.size) { 0 -> { - if(min == 0) { + if (min == 0) { VerificationResult.OK(listOf()) } else if (allCallsForMock.isEmpty()) { VerificationResult.Failure("$callIdxMsg was not called") @@ -106,7 +106,7 @@ open class UnorderedCallVerifier( VerificationResult.OK(listOf(onlyCall)) } else { VerificationResult.Failure( - "$callIdxMsg. One matching call found, but needs at least $min${atMostMsg(max)} calls" + + "$callIdxMsg. One matching call found, but needs ${callsBoundsMsg(min, max)}" + "\nCall: " + allCallsForMock.first() + if (MockKSettings.stackTracesOnVerify) "\nStack trace:\n" + stackTrace(0, allCallsForMock.first().callStack()) @@ -148,8 +148,7 @@ open class UnorderedCallVerifier( }) } else { VerificationResult.Failure( - "$callIdxMsg. $n matching calls found, " + - "but needs at least $min${atMostMsg(max)} calls" + + "$callIdxMsg. $n matching calls found, but needs ${callsBoundsMsg(min, max)}" + "\nCalls:\n" + formatCalls(allCallsForMock) + "\n" + @@ -172,11 +171,15 @@ open class UnorderedCallVerifier( return result } - override fun captureArguments() { - captureBlocks.forEach { it() } - } + override fun captureArguments() = captureBlocks.forEach { it() } - private fun atMostMsg(max: Int) = if (max == Int.MAX_VALUE) "" else " and at most $max" + private fun callsBoundsMsg(min: Int, max: Int): String { + return when { + max == Int.MAX_VALUE -> "at least $min calls" + min == max -> "exactly $min calls" + else -> "at least $min and at most $max calls" + } + } private fun describeArgumentDifference( matcher: InvocationMatcher, diff --git a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/VerificationHelpers.kt b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/VerificationHelpers.kt index bdddaefb8..542b47018 100644 --- a/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/VerificationHelpers.kt +++ b/modules/mockk/src/commonMain/kotlin/io/mockk/impl/verify/VerificationHelpers.kt @@ -9,20 +9,18 @@ import io.mockk.impl.InternalPlatform import io.mockk.impl.stub.StubRepository object VerificationHelpers { - fun formatCalls(calls: List, verifiedCalls: List = listOf()): String { - return calls.mapIndexed { idx, call -> + fun formatCalls(calls: List, verifiedCalls: List = listOf()): String = + calls.mapIndexed { idx, call -> val plusSign = (if (verifiedCalls.contains(call)) "+" else "") "${idx + 1}) $plusSign$call" }.joinToString("\n") - } - fun stackTraces(calls: List): String { - return calls.mapIndexed { idx, call -> + fun stackTraces(calls: List): String = + calls.mapIndexed { idx, call -> val prefix = "${idx + 1})" "$prefix ${stackTrace(prefix.length + 1, call.callStack())}" }.joinToString("\n\n") - } fun stackTrace(prefix: Int, stackTrace: List): String { @Suppress("DEPRECATION_ERROR") @@ -74,16 +72,15 @@ object VerificationHelpers { matchers: List, allCalls: List, lcs: LCSMatchingAlgo = LCSMatchingAlgo(allCalls, matchers).apply { lcs() } - ): String { - return "\n\nMatchers: \n" + formatMatchers(matchers, lcs.verifiedMatchers) + - "\n\nCalls:\n" + - formatCalls(allCalls, lcs.verifiedCalls) + - "\n" + - if (MockKSettings.stackTracesOnVerify) - "\nStack traces:\n" + stackTraces(allCalls) - else - "" - } + ): String = + "\n\nMatchers: \n" + formatMatchers(matchers, lcs.verifiedMatchers) + + "\n\nCalls:\n" + + formatCalls(allCalls, lcs.verifiedCalls) + + "\n" + + if (MockKSettings.stackTracesOnVerify) + "\nStack traces:\n" + stackTraces(allCalls) + else + "" private fun formatMatchers(matchers: List, verifiedMatchers: List) = matchers.joinToString("\n") { diff --git a/modules/mockk/src/commonTest/kotlin/io/mockk/impl/stub/MockKStubTest.kt b/modules/mockk/src/commonTest/kotlin/io/mockk/impl/stub/MockKStubTest.kt index 023964d34..9d685e556 100644 --- a/modules/mockk/src/commonTest/kotlin/io/mockk/impl/stub/MockKStubTest.kt +++ b/modules/mockk/src/commonTest/kotlin/io/mockk/impl/stub/MockKStubTest.kt @@ -44,7 +44,20 @@ $mock.function(eq(5))))""" assertEquals(expectedMessage, exception.message) } + @Test + fun testNull() { + val mock: DummyClass = mockk() + + every { + mock.functionNull(anyNullable()) + } returns 3 + + mock.functionNull(null) + } + + class DummyClass { fun function(a: Int) = a + fun functionNull(a: Any?) = 2 } } \ No newline at end of file diff --git a/modules/mockk/src/commonTest/kotlin/io/mockk/it/ClearMocksTest.kt b/modules/mockk/src/commonTest/kotlin/io/mockk/it/ClearMocksTest.kt index 56dca1051..17f7b88ab 100644 --- a/modules/mockk/src/commonTest/kotlin/io/mockk/it/ClearMocksTest.kt +++ b/modules/mockk/src/commonTest/kotlin/io/mockk/it/ClearMocksTest.kt @@ -67,4 +67,27 @@ class ClearMocksTest { assertEquals(55, topLevelFn()) assertEquals(5, obj.op(4)) } + + @Test + fun clearAllMocksCurrentThreadOnly() { + var mockInOtherThread: MockCls? = null + + val thread = Thread { + mockInOtherThread = mockk() + every { mockInOtherThread!!.op(any()) } returns 42 + } + thread.start() + thread.join() + + every { mock.op(any()) } returns 24 + assertEquals(24, mock.op(1)) + assertEquals(42, mockInOtherThread?.op(1)) + + clearAllMocks(currentThreadOnly = true) + + // Current thread's mock is cleared; + assertEquals(2, mock.op(1)) + // The other thread's mock remains. + assertEquals(42, mockInOtherThread?.op(1)) + } } \ No newline at end of file diff --git a/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt b/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt index b1628af82..7f03cadfb 100644 --- a/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt +++ b/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt @@ -1,10 +1,12 @@ package io.mockk.it +import io.mockk.coEvery import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.spyk import io.mockk.verify +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.assertTimeoutPreemptively import java.time.Duration import java.util.UUID @@ -569,6 +571,24 @@ class ValueClassTest { assertEquals(dummyValueClassReturn, result) } + @Test + fun `function returning nullable value class not boxed due to cast to another type`() { + val mock = mockk { + every { nullableValueClass() } returns DummyValue(2) + } + + assertEquals(DummyValue(2), mock.nullableValueClass()) + } + + @Test + fun `nullable value class field is not boxed due to cast to another type`() { + val mock = mockk { + every { nullableValueClassField } returns DummyValue(2) + } + + assertEquals(DummyValue(2), mock.nullableValueClassField) + } + @Test fun `receiver is ValueClass, return is String`() { val fn = mockk String>() @@ -621,6 +641,40 @@ class ValueClassTest { assertEquals(DummyValue(3), result) } + @Test + fun `spy class returning value class boxed due to suspend function`() { + val f = spyk() + val result = runBlocking { f.returnValueClassSuspendNotInlined() } + + assertEquals(DummyValue(0), result) + } + + @Test + fun `mock class returning value class boxed due to suspend function`() { + val f = mockk() + coEvery { f.returnValueClassSuspendNotInlined() } returns DummyValue(3) + val result = runBlocking { f.returnValueClassSuspendNotInlined() } + + assertEquals(DummyValue(3), result) + } + + @Test + fun `spy class returning complex value class not boxed due to suspend function`() { + val f = spyk() + val result = runBlocking { f.returnComplexValueClassSuspendInlined() } + + assertEquals(ComplexValue(UUID.fromString("c5744ead-302f-4e29-9f82-d10eb2a85ea3")), result) + } + + @Test + fun `mock class returning complex value class not boxed due to suspend function`() { + val f = mockk() + coEvery { f.returnComplexValueClassSuspendInlined() } returns ComplexValue(UUID.fromString("bca61f8d-ba4d-475f-8dc6-08b943836998")) + val result = runBlocking { f.returnComplexValueClassSuspendInlined() } + + assertEquals(ComplexValue(UUID.fromString("bca61f8d-ba4d-475f-8dc6-08b943836998")), result) + } + companion object { @JvmInline @@ -640,6 +694,7 @@ class ValueClassTest { @Suppress("UNUSED_PARAMETER") class DummyService { val valueClassField = DummyValue(0) + val nullableValueClassField : DummyValue? = null fun argWrapperReturnWrapper(wrapper: DummyValueWrapper): DummyValueWrapper = DummyValueWrapper(DummyValue(0)) @@ -662,7 +717,16 @@ class ValueClassTest { // Note the value class is not inlined in this case due to being cast to another type fun returnValueClassNotInlined(): Any = DummyValue(0) + @Suppress("RedundantSuspendModifier") + suspend fun returnValueClassSuspendNotInlined(): DummyValue = DummyValue(0) + + @Suppress("RedundantSuspendModifier") + suspend fun returnComplexValueClassSuspendInlined(): ComplexValue = + ComplexValue(UUID.fromString("c5744ead-302f-4e29-9f82-d10eb2a85ea3")) + fun argNoneReturnsUInt(): UInt = 123u + + fun nullableValueClass(): DummyValue? = null } } } diff --git a/modules/mockk/src/commonTest/kotlin/io/mockk/it/VerificationErrorsTest.kt b/modules/mockk/src/commonTest/kotlin/io/mockk/it/VerificationErrorsTest.kt index 72c1cee0e..ebcf225f3 100644 --- a/modules/mockk/src/commonTest/kotlin/io/mockk/it/VerificationErrorsTest.kt +++ b/modules/mockk/src/commonTest/kotlin/io/mockk/it/VerificationErrorsTest.kt @@ -88,6 +88,54 @@ class VerificationErrorsTest { } } + @Test + fun someMatchingCallsFoundButTooMuch() { + expectVerificationError("4 matching calls found, but needs at least 2 and at most 3 calls", "MockCls.otherOp") { + every { mock.otherOp(1, any()) } answers { 2 + firstArg() } + + mock.otherOp(1, 2) + mock.otherOp(1, 2) + mock.otherOp(1, 2) + mock.otherOp(1, 2) + + verify(atLeast = 2, atMost = 3) { mock.otherOp(1, 2) } + } + } + + @Test + fun oneMatchingCallFoundButNeedMoreInRange() { + expectVerificationError("One matching call found, but needs at least 2 and at most 3 calls", "MockCls.otherOp") { + every { mock.otherOp(1, any()) } answers { 2 + firstArg() } + + mock.otherOp(1, 2) + + verify(atLeast = 2, atMost = 3) { mock.otherOp(1, 2) } + } + } + + @Test + fun someMatchingCallsFoundButNeedExactMatch() { + expectVerificationError("2 matching calls found, but needs exactly 3 calls", "MockCls.otherOp") { + every { mock.otherOp(1, any()) } answers { 2 + firstArg() } + + mock.otherOp(1, 2) + mock.otherOp(1, 2) + + verify(exactly = 3) { mock.otherOp(1, 2) } + } + } + + @Test + fun oneMatchingCallFoundButNeedExactMatch() { + expectVerificationError("One matching call found, but needs exactly 2 calls", "MockCls.otherOp") { + every { mock.otherOp(1, any()) } answers { 2 + firstArg() } + + mock.otherOp(1, 2) + + verify(exactly = 2) { mock.otherOp(1, 2) } + } + } + @Test fun callsAreNotInVerificationOrder() { expectVerificationError("calls are not in verification order", "MockCls.otherOp") { diff --git a/modules/mockk/src/jsMain/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt b/modules/mockk/src/jsMain/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt index 16e43f291..3531eaa6e 100644 --- a/modules/mockk/src/jsMain/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt +++ b/modules/mockk/src/jsMain/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt @@ -1,7 +1,6 @@ package io.mockk.impl.instantiation import io.mockk.MethodDescription -import io.mockk.impl.log.Logger import io.mockk.impl.stub.Stub import io.mockk.impl.stub.StubGatewayAccess import io.mockk.impl.stub.StubRepository @@ -36,9 +35,6 @@ class JsMockFactory( } - companion object { - val log = Logger() - } } internal external interface ProxyHandler { diff --git a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmConstructorMockFactory.kt b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmConstructorMockFactory.kt index 8fe726d7c..6440e412a 100644 --- a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmConstructorMockFactory.kt +++ b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmConstructorMockFactory.kt @@ -272,8 +272,8 @@ class JvmConstructorMockFactory( getMockVariant(type)?.clear(options) } - override fun clearAll(options: MockKGateway.ClearOptions) { - clearer.clearAll(options) + override fun clearAll(options: MockKGateway.ClearOptions, currentThreadOnly: Boolean) { + clearer.clearAll(options, currentThreadOnly) } fun isMock(cls: KClass<*>) = synchronized(handlers) { diff --git a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt index 94b65bada..93b3be12a 100644 --- a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt +++ b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt @@ -4,6 +4,7 @@ import io.mockk.* import io.mockk.impl.InternalPlatform import io.mockk.impl.stub.Stub import io.mockk.core.ValueClassSupport.boxedClass +import io.mockk.core.ValueClassSupport.maybeUnboxValueForMethodReturn import io.mockk.proxy.MockKInvocationHandler import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method @@ -149,18 +150,20 @@ object JvmMockFactoryHelper { val kotlinReturnType = kotlinFunc?.returnType ?: findBackingField(declaringClass.kotlin, this)?.returnType - val returnType: KClass<*> = when (kotlinReturnType) { + val returnTypeNullable = kotlinReturnType?.isMarkedNullable ?: false + val returnTypeClass: KClass<*> = when (kotlinReturnType) { is KType -> kotlinReturnType.classifier as? KClass<*> ?: returnType.kotlin is KClass<*> -> kotlinReturnType else -> returnType.kotlin - }.boxedClass + } + + val returnType = if (!returnTypeNullable) returnTypeClass.boxedClass else returnTypeClass val androidCompatibleReturnType = if (returnType.qualifiedName in androidUnsupportedTypes) { this@toDescription.returnType.kotlin } else { returnType } - val returnTypeNullable = kotlinReturnType?.isMarkedNullable ?: false val result = MethodDescription( name, diff --git a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmObjectMockFactory.kt b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmObjectMockFactory.kt index 31de39371..7452c92dc 100644 --- a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmObjectMockFactory.kt +++ b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmObjectMockFactory.kt @@ -81,9 +81,14 @@ class JvmObjectMockFactory( } override fun clearAll( - options: MockKGateway.ClearOptions + options: MockKGateway.ClearOptions, + currentThreadOnly: Boolean ) { + val currentThreadId = Thread.currentThread().id stubRepository.allStubs.forEach { + if (currentThreadOnly && currentThreadId != it.threadId) { + return@forEach + } it.clear(options) } } diff --git a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmStaticMockFactory.kt b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmStaticMockFactory.kt index 48a75f904..9e9d2f44a 100644 --- a/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmStaticMockFactory.kt +++ b/modules/mockk/src/jvmMain/kotlin/io/mockk/impl/instantiation/JvmStaticMockFactory.kt @@ -67,9 +67,16 @@ class JvmStaticMockFactory( } override fun clearAll( - options: MockKGateway.ClearOptions + options: MockKGateway.ClearOptions, + currentThreadOnly: Boolean ) { - stubRepository.allStubs.forEach { it.clear(options) } + val currentThreadId = Thread.currentThread().id + stubRepository.allStubs.forEach { + if (currentThreadOnly && currentThreadId != it.threadId) { + return@forEach + } + it.clear(options) + } } companion object { diff --git a/modules/mockk/src/jvmMain/kotlin/io/mockk/junit5/MockKExtension.kt b/modules/mockk/src/jvmMain/kotlin/io/mockk/junit5/MockKExtension.kt index 8f849e0ab..95e5ae000 100644 --- a/modules/mockk/src/jvmMain/kotlin/io/mockk/junit5/MockKExtension.kt +++ b/modules/mockk/src/jvmMain/kotlin/io/mockk/junit5/MockKExtension.kt @@ -6,6 +6,7 @@ import io.mockk.impl.annotations.InjectMockKs import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.RelaxedMockK import io.mockk.impl.annotations.SpyK +import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.extension.* import java.lang.annotation.Inherited import java.lang.reflect.AnnotatedElement @@ -16,20 +17,22 @@ import kotlin.reflect.KClass import kotlin.reflect.jvm.javaConstructor /** - * JUnit5 extension. + * MockK JUnit5 extension. * * Parameters can use [MockK] and [RelaxedMockK]. - * Class properties can use [MockK], [RelaxedMockK] and [SpyK] - * [unmockkAll] will be called after test class execution (*) + * Class properties can use [MockK], [RelaxedMockK] and [SpyK]. * - * Usage: declare @ExtendWith(MockKExtension.class) on a test class + * [unmockkAll] will be called after each test function execution if the test lifecycle is set to + * [TestInstance.Lifecycle.PER_METHOD] (JUnit 5 default), or after all test functions in the test class have been + * executed if the test lifecycle is set to [TestInstance.Lifecycle.PER_CLASS]. + * [unmockkAll] will not be called if the [KeepMocks] annotation is present or if the `mockk.junit.extension.keepmocks` + * Gradle property is set to `true`. * - * Alternatively `–Djunit.extensions.autodetection.enabled=true` may be placed on a command line. + * Usage: declare `@ExtendWith(MockKExtension::class)` on a test class. * - * (*) [unmockkAll] default behavior can be disabled by adding [KeepMocks] to your test class or method or - * `–Dmockk.junit.extension.keepmocks=true` on a command line + * Alternatively `–Djunit.extensions.autodetection.enabled=true` may be placed on a command line. */ -class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCallback { +class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterEachCallback, AfterAllCallback { private val cache = mutableMapOf, Any>() override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { @@ -50,6 +53,7 @@ class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCal *moreInterfaces(parameterContext), recordPrivateCalls = annotation.recordPrivateCalls ) + is MockK, is RelaxedMockK -> { mockkClass( type, @@ -59,6 +63,7 @@ class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCal relaxUnitFun = (annotation as? MockK)?.relaxUnitFun ?: false, ) } + else -> null }?.also { cache[type] = it } } @@ -109,7 +114,19 @@ class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCal MockKAnnotations.init(testInstance) } + override fun afterEach(context: ExtensionContext) { + if (context.isTestInstanceLifecyclePerMethod) { + finish(context) + } + } + override fun afterAll(context: ExtensionContext) { + if (!context.isTestInstanceLifecyclePerMethod) { + finish(context) + } + } + + private fun finish(context: ExtensionContext) { if (!context.keepMocks) { unmockkAll() } @@ -130,6 +147,9 @@ class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCal } } + private val ExtensionContext.isTestInstanceLifecyclePerMethod: Boolean + get() = testInstanceLifecycle.map { it == TestInstance.Lifecycle.PER_METHOD }.orElse(true) + private val ExtensionContext.keepMocks: Boolean get() = testClass.keepMocks || testMethod.keepMocks || getConfigurationParameter(KEEP_MOCKS_PROPERTY).map { it.toBoolean() }.orElse(false) @@ -143,7 +163,7 @@ class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCal getConfigurationParameter(CONFIRM_VERIFICATION_PROPERTY).map { it.toBoolean() }.orElse(false) private val Optional.confirmVerification - get() = map { it.getAnnotation(ConfirmVerification::class.java) != null} + get() = map { it.getAnnotation(ConfirmVerification::class.java) != null } .orElse(false) private val ExtensionContext.checkUnnecessaryStub: Boolean @@ -151,11 +171,12 @@ class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCal getConfigurationParameter(CHECK_UNNECESSARY_STUB_PROPERTY).map { it.toBoolean() }.orElse(false) private val Optional.checkUnnecessaryStub - get() = map { it.getAnnotation(CheckUnnecessaryStub::class.java) != null} + get() = map { it.getAnnotation(CheckUnnecessaryStub::class.java) != null } .orElse(false) private val ExtensionContext.requireParallelTesting: Boolean - get() = getConfigurationParameter(REQUIRE_PARALLEL_TESTING).map { it.toBoolean() }.orElse(false) + get() = testClass.requireParallelTesting || + getConfigurationParameter(REQUIRE_PARALLEL_TESTING).map { it.toBoolean() }.orElse(false) private val Optional.requireParallelTesting get() = map { it.getAnnotation(RequireParallelTesting::class.java) != null } @@ -193,5 +214,4 @@ class MockKExtension : TestInstancePostProcessor, ParameterResolver, AfterAllCal const val CHECK_UNNECESSARY_STUB_PROPERTY = "mockk.junit.extension.checkUnnecessaryStub" const val REQUIRE_PARALLEL_TESTING = "mockk.junit.extension.requireParallelTesting" } - } diff --git a/modules/mockk/src/jvmTest/kotlin/io/mockk/junit5/MockKExtensionTest.kt b/modules/mockk/src/jvmTest/kotlin/io/mockk/junit5/MockKExtensionTest.kt index 5cdba8588..5553b21fd 100644 --- a/modules/mockk/src/jvmTest/kotlin/io/mockk/junit5/MockKExtensionTest.kt +++ b/modules/mockk/src/jvmTest/kotlin/io/mockk/junit5/MockKExtensionTest.kt @@ -102,5 +102,4 @@ class MockKExtensionTest { verify { carSpy.relaxedTest() } } - } diff --git a/test-modules/performance-tests/build.gradle.kts b/test-modules/performance-tests/build.gradle.kts index dc79b6d68..141d266cd 100644 --- a/test-modules/performance-tests/build.gradle.kts +++ b/test-modules/performance-tests/build.gradle.kts @@ -4,11 +4,11 @@ import kotlinx.benchmark.gradle.benchmark plugins { buildsrc.convention.`kotlin-jvm` - id("org.jetbrains.kotlinx.benchmark") version "0.4.5" + id("org.jetbrains.kotlinx.benchmark") version "0.4.12" } dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.5") + implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.12") implementation(platform(Deps.Libs.kotlinCoroutinesBom)) implementation(Deps.Libs.kotlinCoroutinesCore) implementation(projects.modules.mockk) @@ -26,7 +26,7 @@ benchmark { targets { register("main") { this as JvmBenchmarkTarget - jmhVersion = "1.35" + jmhVersion = "1.37" } } } diff --git a/test-modules/performance-tests/src/main/kotlin/io/mockk/performance/JmhTest.kt b/test-modules/performance-tests/src/main/kotlin/io/mockk/performance/JmhTest.kt index ff6e6595a..0de74a4bf 100644 --- a/test-modules/performance-tests/src/main/kotlin/io/mockk/performance/JmhTest.kt +++ b/test-modules/performance-tests/src/main/kotlin/io/mockk/performance/JmhTest.kt @@ -1,34 +1,80 @@ package io.mockk.performance +import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.benchmark.Scope +import kotlinx.benchmark.State +import kotlinx.benchmark.TearDown import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.BenchmarkMode +import org.openjdk.jmh.annotations.Level import org.openjdk.jmh.annotations.Mode import org.openjdk.jmh.infra.Blackhole +@State(Scope.Thread) @BenchmarkMode(Mode.Throughput) open class JmhTest { + @TearDown(Level.Invocation) + fun tearDown() = unmockkAll() + + private fun noMockOrStub() = MockedClass() + private fun simpleMock() = mockk() + private fun simpleMockAndStub() = mockk { every { mockedFun() } returns "Hello, mockk!" } + + private fun mockThenOperation( + blackhole: Blackhole, + mockedClassFactory: () -> MockedClass, + operation: () -> Unit, + ) { + blackhole.consume(mockedClassFactory()) + operation() + } + @Benchmark fun noMockOrStub(blackhole: Blackhole) { - val mockedClass = MockedClass() + val mockedClass = noMockOrStub() blackhole.consume(mockedClass) } @Benchmark fun simpleMock(blackhole: Blackhole) { - val mockedClass: MockedClass = mockk() + val mockedClass = simpleMock() blackhole.consume(mockedClass) } @Benchmark fun simpleMockAndStub(blackhole: Blackhole) { - val mockedClass: MockedClass = mockk() - every { mockedClass.mockedFun() } returns "Hello, mockk!" + val mockedClass = simpleMockAndStub() blackhole.consume(mockedClass) } + @Benchmark + fun clearAllMocksAfterNoMockOrStub(blackhole: Blackhole) = + mockThenOperation(blackhole, ::noMockOrStub) { clearAllMocks() } + + @Benchmark + fun clearAllMocksAfterSimpleMock(blackhole: Blackhole) = + mockThenOperation(blackhole, ::simpleMock) { clearAllMocks() } + + @Benchmark + fun clearAllMocksAfterSimpleMockAndStub(blackhole: Blackhole) = + mockThenOperation(blackhole, ::simpleMockAndStub) { clearAllMocks() } + + @Benchmark + fun unmockkAllAfterNoMockOrStub(blackhole: Blackhole) = + mockThenOperation(blackhole, ::noMockOrStub) { unmockkAll() } + + @Benchmark + fun unmockkAllAfterSimpleMock(blackhole: Blackhole) = + mockThenOperation(blackhole, ::simpleMock) { unmockkAll() } + + @Benchmark + fun unmockkAllAfterSimpleMockAndStub(blackhole: Blackhole) = + mockThenOperation(blackhole, ::simpleMockAndStub) { unmockkAll() } + class MockedClass { fun mockedFun(): String = "Hello, world!" }