From 40bb77bbf0e1e1f3126822596bcba677f2284e1f Mon Sep 17 00:00:00 2001 From: Eugene Platonov Date: Sun, 8 Feb 2026 23:46:45 -0500 Subject: [PATCH] Refactor to separate Scala 2 WeakTypeTag code from shared runtime code --- .../scala-2/org/mockito/MockCreator.scala | 81 +++++++++ .../scala-2/org/mockito/MockitoEnhancer.scala | 9 + .../org/mockito/ReflectionUtils.scala | 0 .../main/scala/org/mockito/MockitoAPI.scala | 159 ++++++------------ .../ResetMocksAfterEachTestCompat.scala} | 19 +-- .../ResetMocksAfterEachAsyncTest.scala | 4 +- .../scalatest/ResetMocksAfterEachTest.scala | 4 +- .../ResetMocksAfterEachTestRuntime.scala | 20 +++ .../org/mockito/IdiomaticStubbingTest.scala | 22 +++ .../scala/user/org/mockito/TestModel.scala | 12 ++ 10 files changed, 201 insertions(+), 129 deletions(-) create mode 100644 common/src/main/scala-2/org/mockito/MockCreator.scala create mode 100644 common/src/main/scala-2/org/mockito/MockitoEnhancer.scala rename common/src/main/{scala => scala-2}/org/mockito/ReflectionUtils.scala (100%) rename scalatest/src/main/{scala/org/mockito/scalatest/ResetMocksAfterEachTestBase.scala => scala-2/org/mockito/scalatest/ResetMocksAfterEachTestCompat.scala} (56%) create mode 100644 scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTestRuntime.scala diff --git a/common/src/main/scala-2/org/mockito/MockCreator.scala b/common/src/main/scala-2/org/mockito/MockCreator.scala new file mode 100644 index 00000000..bb18658b --- /dev/null +++ b/common/src/main/scala-2/org/mockito/MockCreator.scala @@ -0,0 +1,81 @@ +package org.mockito + +import org.mockito.internal.handler.ScalaMockHandler +import org.mockito.invocation.MockHandler +import org.mockito.mock.MockCreationSettings +import org.mockito.quality.Strictness +import org.mockito.stubbing.{ Answer, CallsRealMethods, DefaultAnswer } +import org.scalactic.Prettifier + +import scala.reflect.ClassTag +import scala.reflect.runtime.universe.WeakTypeTag + +private[mockito] trait MockCreator extends MockCreatorRuntime { + + /** + * Delegates to Mockito.mock(type: Class[T]) It provides a nicer API as you can, for instance, do mock[MyClass] instead of + * mock(classOf[MyClass]) + * + * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def + * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") + * + * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like + * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... + */ + def mock[T <: AnyRef: ClassTag: WeakTypeTag](implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): T = + mock[T](defaultAnswer) + + def mock[T <: AnyRef: ClassTag: WeakTypeTag](defaultAnswer: Answer[?])(implicit $pt: Prettifier): T = + mock[T](DefaultAnswer(defaultAnswer)) + + /** + * Delegates to Mockito.mock(type: Class[T], defaultAnswer: Answer[_]) It provides a nicer API as you can, for instance, do mock[MyClass](defaultAnswer) + * instead of mock(classOf[MyClass], defaultAnswer) + * + * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def + * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") + * + * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like + * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... + */ + def mock[T <: AnyRef: ClassTag: WeakTypeTag](defaultAnswer: DefaultAnswer)(implicit $pt: Prettifier): T = + mock[T](withSettings(defaultAnswer)) + + /** + * Delegates to Mockito.mock(type: Class[T], mockSettings: MockSettings) It provides a nicer API as you can, for instance, do + * mock[MyClass](mockSettings) instead of mock(classOf[MyClass], mockSettings) + * + * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def + * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") + * + * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like + * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... + */ + def mock[T <: AnyRef: ClassTag: WeakTypeTag](mockSettings: MockSettings)(implicit $pt: Prettifier): T = + createMock(mockSettings) + + /** + * Delegates to Mockito.mock(type: Class[T], name: String) It provides a nicer API as you can, for instance, do mock[MyClass](name) instead of + * mock(classOf[MyClass], name) + * + * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def + * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") + * + * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like + * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... + */ + def mock[T <: AnyRef: ClassTag: WeakTypeTag](name: String)(implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): T = + mock(withSettings.name(name)) + + def spy[T <: AnyRef: ClassTag: WeakTypeTag](realObj: T, lenient: Boolean = false)(implicit $pt: Prettifier): T = { + val mockSettings: MockSettings = withSettings(CallsRealMethods).spiedInstance(realObj) + val settings = if (lenient) mockSettings.strictness(Strictness.LENIENT) else mockSettings + mock[T](settings) + } + + private[mockito] def createMock[T <: AnyRef: ClassTag: WeakTypeTag]( + mockSettings: MockSettings, + mockHandler: (MockCreationSettings[T], Prettifier) => MockHandler[T] = (settings: MockCreationSettings[T], pt: Prettifier) => ScalaMockHandler(settings)(pt) + )(implicit $pt: Prettifier): T = + createMock[T](mockSettings, ReflectionUtils.extraInterfaces[T], mockHandler) +} diff --git a/common/src/main/scala-2/org/mockito/MockitoEnhancer.scala b/common/src/main/scala-2/org/mockito/MockitoEnhancer.scala new file mode 100644 index 00000000..4c989c55 --- /dev/null +++ b/common/src/main/scala-2/org/mockito/MockitoEnhancer.scala @@ -0,0 +1,9 @@ +package org.mockito + +/** + * Scala 2 specific trait that provides object mocking functionality. Extends MockCreator (Scala 2 version with WeakTypeTag) and MockitoEnhancerRuntime (shared utilities including + * withObject methods). + * + * This is a thin compatibility layer - all actual functionality is in MockCreator and MockitoEnhancerRuntime. + */ +private[mockito] trait MockitoEnhancer extends MockCreator with MockitoEnhancerRuntime diff --git a/common/src/main/scala/org/mockito/ReflectionUtils.scala b/common/src/main/scala-2/org/mockito/ReflectionUtils.scala similarity index 100% rename from common/src/main/scala/org/mockito/ReflectionUtils.scala rename to common/src/main/scala-2/org/mockito/ReflectionUtils.scala diff --git a/common/src/main/scala/org/mockito/MockitoAPI.scala b/common/src/main/scala/org/mockito/MockitoAPI.scala index d4105ab9..c43175bd 100644 --- a/common/src/main/scala/org/mockito/MockitoAPI.scala +++ b/common/src/main/scala/org/mockito/MockitoAPI.scala @@ -21,7 +21,7 @@ import org.mockito.internal.stubbing.answers.ScalaThrowsException import org.mockito.internal.util.MockUtil import org.mockito.internal.util.reflection.LenientCopyTool import org.mockito.internal.{ ValueClassExtractor, ValueClassWrapper } -import org.mockito.invocation.{ Invocation, InvocationContainer, InvocationOnMock, MockHandler } +import org.mockito.invocation.{ InvocationOnMock, MockHandler } import org.mockito.mock.MockCreationSettings import org.mockito.quality.Strictness import org.mockito.stubbing.* @@ -30,29 +30,59 @@ import org.scalactic.{ Equality, Prettifier } import scala.jdk.CollectionConverters.* import scala.reflect.ClassTag -import scala.reflect.runtime.universe.WeakTypeTag private[mockito] trait ScalacticSerialisableHack { // Hack until Equality can be made serialisable implicit def mockitoSerialisableEquality[T]: Equality[T] = serialisableEquality[T] } -private[mockito] trait MockCreator { - def mock[T <: AnyRef: ClassTag: WeakTypeTag](implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): T - def mock[T <: AnyRef: ClassTag: WeakTypeTag](defaultAnswer: Answer[?])(implicit $pt: Prettifier): T = - mock[T](DefaultAnswer(defaultAnswer)) - def mock[T <: AnyRef: ClassTag: WeakTypeTag](defaultAnswer: DefaultAnswer)(implicit $pt: Prettifier): T - def mock[T <: AnyRef: ClassTag: WeakTypeTag](mockSettings: MockSettings)(implicit $pt: Prettifier): T - def mock[T <: AnyRef: ClassTag: WeakTypeTag](name: String)(implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): T - - def spy[T <: AnyRef: ClassTag: WeakTypeTag](realObj: T, lenient: Boolean)(implicit $pt: Prettifier): T - def spyLambda[T <: AnyRef: ClassTag](realObj: T): T +/** + * Runtime support for MockCreator. Provides createMock implementation using only ClassTag (Scala 3 compatible). Shared across Scala 2 and Scala 3. Version-specific MockCreator + * traits extend this. + */ +private[mockito] trait MockCreatorRuntime { /** * Delegates to Mockito.withSettings(), it's only here to expose the full Mockito API */ def withSettings(implicit defaultAnswer: DefaultAnswer): MockSettings = Mockito.withSettings().defaultAnswer(defaultAnswer) + + private[mockito] def createMock[T <: AnyRef: ClassTag]( + mockSettings: MockSettings, + interfaces: List[Class[?]], + mockHandler: (MockCreationSettings[T], Prettifier) => MockHandler[T] + )(implicit $pt: Prettifier): T = { + val realClass: Class[T] = mockSettings match { + case m: MockSettingsImpl[?] if !m.getExtraInterfaces.isEmpty => + throw new IllegalArgumentException("If you want to add extra traits to the mock use the syntax mock[MyClass with MyTrait]") + case m: MockSettingsImpl[?] if m.getSpiedInstance != null => m.getSpiedInstance.getClass.asInstanceOf[Class[T]] + case _ => clazz + } + + val settings = + if (interfaces.nonEmpty) mockSettings.extraInterfaces(interfaces*) + else mockSettings + + def createMock(settings: MockCreationSettings[T]): T = { + val mock = getMockMaker.createMock(settings, mockHandler(settings, $pt)) + val spiedInstance = settings.getSpiedInstance + if (spiedInstance != null) new LenientCopyTool().copyToMock(spiedInstance, mock) + mock + } + + settings match { + case s: MockSettingsImpl[?] => + val creationSettings = s.build[T](realClass) + val mock = createMock(creationSettings) + mockingProgress.mockingStarted(mock, creationSettings) + mock + case _ => + throw new IllegalArgumentException(s"""Unexpected implementation of '${settings.getClass.getCanonicalName}' + |At the moment, you cannot provide your own implementations of that class.""".stripMargin) + } + } + } //noinspection MutatorLikeMethodIsParameterless @@ -451,101 +481,10 @@ private[mockito] trait DoSomething { // } } -private[mockito] trait MockitoEnhancer extends MockCreator { - - /** - * Delegates to Mockito.mock(type: Class[T]) It provides a nicer API as you can, for instance, do mock[MyClass] instead of - * mock(classOf[MyClass]) - * - * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def - * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") - * - * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like - * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... - */ - override def mock[T <: AnyRef: ClassTag: WeakTypeTag](implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): T = - createMock(withSettings) - - /** - * Delegates to Mockito.mock(type: Class[T], defaultAnswer: Answer[_]) It provides a nicer API as you can, for instance, do mock[MyClass](defaultAnswer) - * instead of mock(classOf[MyClass], defaultAnswer) - * - * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def - * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") - * - * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like - * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... - */ - override def mock[T <: AnyRef: ClassTag: WeakTypeTag](defaultAnswer: DefaultAnswer)(implicit $pt: Prettifier): T = - createMock(withSettings(defaultAnswer)) - - /** - * Delegates to Mockito.mock(type: Class[T], mockSettings: MockSettings) It provides a nicer API as you can, for instance, do - * mock[MyClass](mockSettings) instead of mock(classOf[MyClass], mockSettings) - * - * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def - * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") - * - * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like - * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... - */ - override def mock[T <: AnyRef: ClassTag: WeakTypeTag](mockSettings: MockSettings)(implicit $pt: Prettifier): T = - createMock(mockSettings) - - private def createMock[T <: AnyRef: ClassTag: WeakTypeTag]( - mockSettings: MockSettings, - mockHandler: (MockCreationSettings[T], Prettifier) => MockHandler[T] = (settings: MockCreationSettings[T], pt: Prettifier) => ScalaMockHandler(settings)(pt) - )(implicit $pt: Prettifier): T = { - val interfaces = ReflectionUtils.extraInterfaces - - val realClass: Class[T] = mockSettings match { - case m: MockSettingsImpl[?] if !m.getExtraInterfaces.isEmpty => - throw new IllegalArgumentException("If you want to add extra traits to the mock use the syntax mock[MyClass with MyTrait]") - case m: MockSettingsImpl[?] if m.getSpiedInstance != null => m.getSpiedInstance.getClass.asInstanceOf[Class[T]] - case _ => clazz - } - - val settings = - if (interfaces.nonEmpty) mockSettings.extraInterfaces(interfaces*) - else mockSettings - - def createMock(settings: MockCreationSettings[T]): T = { - val mock = getMockMaker.createMock(settings, mockHandler(settings, $pt)) - val spiedInstance = settings.getSpiedInstance - if (spiedInstance != null) new LenientCopyTool().copyToMock(spiedInstance, mock) - mock - } - - settings match { - case s: MockSettingsImpl[?] => - val creationSettings = s.build[T](realClass) - val mock = createMock(creationSettings) - mockingProgress.mockingStarted(mock, creationSettings) - mock - case _ => - throw new IllegalArgumentException(s"""Unexpected implementation of '${settings.getClass.getCanonicalName}' - |At the moment, you cannot provide your own implementations of that class.""".stripMargin) - } - } - - /** - * Delegates to Mockito.mock(type: Class[T], name: String) It provides a nicer API as you can, for instance, do mock[MyClass](name) instead of - * mock(classOf[MyClass], name) - * - * It also pre-stub the mock so the compiler-generated methods that provide the values for the default arguments are called, ie: given def - * iHaveSomeDefaultArguments(noDefault: String, default: String = "default value") - * - * without this fix, if you call it as iHaveSomeDefaultArguments("I'm not gonna pass the second argument") then you could have not verified it like - * verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value") as the value for the second parameter would have been null... - */ - override def mock[T <: AnyRef: ClassTag: WeakTypeTag](name: String)(implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): T = - mock(withSettings.name(name)) - - def spy[T <: AnyRef: ClassTag: WeakTypeTag](realObj: T, lenient: Boolean = false)(implicit $pt: Prettifier): T = { - def mockSettings: MockSettings = Mockito.withSettings().defaultAnswer(CALLS_REAL_METHODS).spiedInstance(realObj) - val settings = if (lenient) mockSettings.strictness(Strictness.LENIENT) else mockSettings - mock[T](settings) - } +/** + * Runtime support for MockitoEnhancer with utility methods that don't require WeakTypeTag. Shared across Scala 2 and Scala 3. Version-specific MockitoEnhancer traits extend this. + */ +private[mockito] trait MockitoEnhancerRuntime extends MockCreatorRuntime { /** * Delegates to Mockito.reset(T... mocks), but restores the default stubs that deal with default argument values @@ -617,7 +556,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator { withObject[O](settings.spiedInstance(_), block) } - private[mockito] def withObject[O <: AnyRef: ClassTag](settings: O => MockSettings, block: => Any)(implicit $pt: Prettifier) = { + private[mockito] def withObject[O <: AnyRef: ClassTag](settings: O => MockSettings, block: => Any)(implicit $pt: Prettifier): Unit = { val objectClass = clazz[O] objectClass.synchronized { val moduleField = objectClass.getDeclaredField("MODULE$") @@ -625,6 +564,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator { val threadAwareMock = createMock( settings(realImpl), + List.empty[Class[?]], (settings: MockCreationSettings[O], pt: Prettifier) => ThreadAwareMockHandler(settings, realImpl)(pt) ) @@ -633,6 +573,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator { finally JavaReflectionUtils.setFinalStatic(moduleField, realImpl) } } + } trait LeniencySettings { @@ -702,7 +643,7 @@ private[mockito] trait Verifications { * * The idea is based on org.scalatest.mockito.MockitoSugar but it adds 100% of the Mockito API * - * It also solve problems like overloaded varargs calls to Java code and pre-stub the mocks so the default arguments in the method parameters work as expected + * It also solves problems like overloaded varargs calls to Java code and pre-stub the mocks so the default arguments in the method parameters work as expected * * @author * Bruno Bonanno diff --git a/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTestBase.scala b/scalatest/src/main/scala-2/org/mockito/scalatest/ResetMocksAfterEachTestCompat.scala similarity index 56% rename from scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTestBase.scala rename to scalatest/src/main/scala-2/org/mockito/scalatest/ResetMocksAfterEachTestCompat.scala index 506e3cec..868e708b 100644 --- a/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTestBase.scala +++ b/scalatest/src/main/scala-2/org/mockito/scalatest/ResetMocksAfterEachTestCompat.scala @@ -1,30 +1,17 @@ package org.mockito.scalatest -import java.util.concurrent.ConcurrentHashMap - import org.mockito.stubbing.DefaultAnswer import org.mockito.{ MockCreator, MockSettings } import org.scalactic.Prettifier -import scala.jdk.CollectionConverters.* import scala.reflect.ClassTag import scala.reflect.runtime.universe.WeakTypeTag /** - * It automatically resets each mock after each test is run, useful when we need to pass the mocks to some framework once at the beginning of the test suite - * - * Just mix-in after your favourite suite, i.e. {{{class MyTest extends PlaySpec with MockitoSugar with ResetMocksAfterEachTest}}} + * Internal Scala 2 compatibility layer for `ResetMocksAfterEachTest`/`ResetMocksAfterEachAsyncTest`. + * Provides WeakTypeTag-based mock override methods that intercept mock creation to track mocks for automatic reset. */ -trait ResetMocksAfterEachTestBase extends MockCreator { self: MockCreator => - - private val mocksToReset = ConcurrentHashMap.newKeySet[AnyRef]().asScala - - protected def resetAll(): Unit = mocksToReset.foreach(org.mockito.MockitoSugar.reset(_)) - - private def addMock[T <: AnyRef](mock: T) = { - mocksToReset.add(mock) - mock - } +private[scalatest] trait ResetMocksAfterEachTestCompat extends MockCreator with ResetMocksAfterEachTestRuntime { abstract override def mock[T <: AnyRef: ClassTag: WeakTypeTag](implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): T = addMock(super.mock[T]) diff --git a/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachAsyncTest.scala b/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachAsyncTest.scala index d8fbce71..474871d6 100644 --- a/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachAsyncTest.scala +++ b/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachAsyncTest.scala @@ -3,11 +3,11 @@ package org.mockito.scalatest import org.scalatest.{ AsyncTestSuite, FutureOutcome } /** - * It automatically resets each mock after a each test is run, useful when we need to pass the mocks to some framework once at the beginning of the test suite + * It automatically resets each mock after each test is run, useful when we need to pass the mocks to some framework once at the beginning of the test suite * * Just mix-in after your favourite suite, i.e. {{{class MyTest extends PlaySpec with MockitoSugar with ResetMocksAfterEachAsyncTest}}} */ -trait ResetMocksAfterEachAsyncTest extends AsyncTestSuite with ResetMocksAfterEachTestBase { +trait ResetMocksAfterEachAsyncTest extends AsyncTestSuite with ResetMocksAfterEachTestCompat { override def withFixture(test: NoArgAsyncTest): FutureOutcome = super.withFixture(test).onCompletedThen(_ => resetAll()) diff --git a/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTest.scala b/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTest.scala index aea899a5..ec0ce159 100644 --- a/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTest.scala +++ b/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTest.scala @@ -3,11 +3,11 @@ package org.mockito.scalatest import org.scalatest.{ Outcome, TestSuite } /** - * It automatically resets each mock after a each test is run, useful when we need to pass the mocks to some framework once at the beginning of the test suite + * It automatically resets each mock after each test is run, useful when we need to pass the mocks to some framework once at the beginning of the test suite * * Just mix-in after your favourite suite, i.e. {{{class MyTest extends PlaySpec with MockitoSugar with ResetMocksAfterEachTest}}} */ -trait ResetMocksAfterEachTest extends TestSuite with ResetMocksAfterEachTestBase { +trait ResetMocksAfterEachTest extends TestSuite with ResetMocksAfterEachTestCompat { override protected def withFixture(test: NoArgTest): Outcome = { val outcome = super.withFixture(test) diff --git a/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTestRuntime.scala b/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTestRuntime.scala new file mode 100644 index 00000000..6aaf5b4a --- /dev/null +++ b/scalatest/src/main/scala/org/mockito/scalatest/ResetMocksAfterEachTestRuntime.scala @@ -0,0 +1,20 @@ +package org.mockito.scalatest + +import java.util.concurrent.ConcurrentHashMap +import scala.jdk.CollectionConverters.* + +/** + * Runtime support for ResetMocksAfterEachTestBase. Shared across Scala 2 and Scala 3. + */ +trait ResetMocksAfterEachTestRuntime { + + private val mocksToReset = ConcurrentHashMap.newKeySet[AnyRef]().asScala + + protected def resetAll(): Unit = mocksToReset.foreach(org.mockito.MockitoSugar.reset(_)) + + private[scalatest] def addMock[T <: AnyRef](mock: T): T = { + mocksToReset.add(mock) + mock + } + +} diff --git a/scalatest/src/test/scala/user/org/mockito/IdiomaticStubbingTest.scala b/scalatest/src/test/scala/user/org/mockito/IdiomaticStubbingTest.scala index b07bd866..1d1f1e60 100644 --- a/scalatest/src/test/scala/user/org/mockito/IdiomaticStubbingTest.scala +++ b/scalatest/src/test/scala/user/org/mockito/IdiomaticStubbingTest.scala @@ -373,5 +373,27 @@ class IdiomaticStubbingTest extends AnyWordSpec with Matchers with ArgumentMatch else FooObject.stateDependantMethod shouldBe now } } + + "stub methods from traits in object" in { + ObjectWithTraits.methodFromTraitA shouldBe "TraitA implementation" + ObjectWithTraits.methodFromTraitB shouldBe 42 + + withObjectMocked[ObjectWithTraits.type] { + // Verify the mock implements the trait interfaces + ObjectWithTraits shouldBe a[TraitA] + ObjectWithTraits shouldBe a[TraitB] + + ObjectWithTraits.methodFromTraitA returns "mocked A" + ObjectWithTraits.methodFromTraitB returns 99 + ObjectWithTraits.ownMethod returns "mocked own" + + ObjectWithTraits.methodFromTraitA shouldBe "mocked A" + ObjectWithTraits.methodFromTraitB shouldBe 99 + ObjectWithTraits.ownMethod shouldBe "mocked own" + } + + ObjectWithTraits.methodFromTraitA shouldBe "TraitA implementation" + ObjectWithTraits.methodFromTraitB shouldBe 42 + } } } diff --git a/scalatest/src/test/scala/user/org/mockito/TestModel.scala b/scalatest/src/test/scala/user/org/mockito/TestModel.scala index b054ca5b..9e8819f1 100644 --- a/scalatest/src/test/scala/user/org/mockito/TestModel.scala +++ b/scalatest/src/test/scala/user/org/mockito/TestModel.scala @@ -135,3 +135,15 @@ object FooObject { def stateDependantMethod: Long = now } + +trait TraitA { + def methodFromTraitA: String = "TraitA implementation" +} + +trait TraitB { + def methodFromTraitB: Int = 42 +} + +object ObjectWithTraits extends TraitA with TraitB { + def ownMethod: String = "own method" +}