Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 24 additions & 34 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import scala.io.Source
import scala.language.postfixOps
import scala.util.Try
import sbt.io.Using

val currentScalaVersion = "2.13.6"

ThisBuild / scalaVersion := currentScalaVersion
inThisBuild(
Seq(
scalaVersion := currentScalaVersion,
//Load version from the file so that Gradle/Shipkit and SBT use the same version
version := sys.env
.get("PROJECT_VERSION")
.filter(_.trim.nonEmpty)
.orElse {
lazy val VersionRE = """^version=(.+)$""".r
Using.file(Source.fromFile)(baseDirectory.value / "version.properties") {
_.getLines.collectFirst { case VersionRE(v) => v }
}
}
.map { _.replace(".*", "-SNAPSHOT") }
.get
)
)

lazy val commonSettings =
Seq(
organization := "org.mockito",
//Load version from the file so that Gradle/Shipkit and SBT use the same version
version := {
val versionFromEnv = System.getenv("PROJECT_VERSION")
if (versionFromEnv != null && versionFromEnv.trim().nonEmpty) {
versionFromEnv
} else {
val pattern = """^version=(.+)$""".r
val source = Source.fromFile("version.properties")
val version = Try(source.getLines.collectFirst { case pattern(v) =>
v
}.get)
source.close
version.get.replace(".*", "-SNAPSHOT")
}
},
crossScalaVersions := Seq(currentScalaVersion, "2.12.14", "2.11.12"),
scalafmtOnCompile := true,
scalacOptions ++= Seq(
Expand Down Expand Up @@ -163,29 +165,17 @@ lazy val core = (project in file("core"))
//TODO remove when we remove the deprecated classes in org.mockito.integrations.Dependencies.scalatest
libraryDependencies += Dependencies.scalatest % "provided",
// include the macro classes and resources in the main jar
mappings in (Compile, packageBin) ++= mappings
.in(macroSub, Compile, packageBin)
.value,
Compile / packageBin / mappings ++= (macroSub / Compile / packageBin / mappings).value,
// include the macro sources in the main source jar
mappings in (Compile, packageSrc) ++= mappings
.in(macroSub, Compile, packageSrc)
.value,
Compile / packageSrc / mappings ++= (macroSub / Compile / packageSrc / mappings).value,
// include the common classes and resources in the main jar
mappings in (Compile, packageBin) ++= mappings
.in(common, Compile, packageBin)
.value,
Compile / packageBin / mappings ++= (common / Compile / packageBin / mappings).value,
// include the common sources in the main source jar
mappings in (Compile, packageSrc) ++= mappings
.in(common, Compile, packageSrc)
.value,
Compile / packageSrc / mappings ++= (common / Compile / packageSrc / mappings).value,
// include the common classes and resources in the main jar
mappings in (Compile, packageBin) ++= mappings
.in(macroCommon, Compile, packageBin)
.value,
Compile / packageBin / mappings ++= (macroCommon / Compile / packageBin / mappings).value,
// include the common sources in the main source jar
mappings in (Compile, packageSrc) ++= mappings
.in(macroCommon, Compile, packageSrc)
.value
Compile / packageSrc / mappings ++= (macroCommon / Compile / packageSrc / mappings).value
)

lazy val macroSub = (project in file("macro"))
Expand Down
34 changes: 31 additions & 3 deletions common/src/main/scala/org/mockito/MockitoAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import org.mockito.mock.MockCreationSettings
import org.mockito.stubbing._
import org.mockito.verification.{ VerificationAfterDelay, VerificationMode, VerificationWithTimeout }
import org.scalactic.{ Equality, Prettifier }

import scala.collection.JavaConverters._
import scala.reflect.ClassTag
import scala.reflect.runtime.universe.WeakTypeTag
Expand Down Expand Up @@ -627,14 +626,29 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
/**
* Mocks the specified object only for the context of the block
*/
def withObjectMocked[O <: AnyRef: ClassTag](block: => Any)(implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): Unit = {
def withObjectMocked[O <: AnyRef: ClassTag](block: => Any)(implicit defaultAnswer: DefaultAnswer, $pt: Prettifier): Unit =
withObject[O](_ => withSettings(defaultAnswer), block)

/**
* Spies the specified object only for the context of the block
*
* Automatically pulls in [[org.mockito.LeniencySettings#strictStubs strict stubbing]] behaviour via implicits.
* To override this default (strict) behaviour, bring lenient settings into implicit scope;
* see [[org.mockito.leniency]] for details
*/
def withObjectSpied[O <: AnyRef: ClassTag](block: => Any)(implicit leniency: LeniencySettings, $pt: Prettifier): Unit = {
val settings = leniency(Mockito.withSettings().defaultAnswer(CALLS_REAL_METHODS))
withObject[O](settings.spiedInstance(_), block)
}

private[mockito] def withObject[O <: AnyRef: ClassTag](settings: O => MockSettings, block: => Any)(implicit $pt: Prettifier) = {
val objectClass = clazz[O]
objectClass.synchronized {
val moduleField = objectClass.getDeclaredField("MODULE$")
val realImpl: O = moduleField.get(null).asInstanceOf[O]

val threadAwareMock = createMock(
withSettings(defaultAnswer),
settings(realImpl),
(settings: MockCreationSettings[O], pt: Prettifier) => ThreadAwareMockHandler(settings, realImpl)(pt)
)

Expand All @@ -645,6 +659,20 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
}
}

trait LeniencySettings {
def apply(settings: MockSettings): MockSettings
}

object LeniencySettings {
implicit val strictStubs: LeniencySettings = new LeniencySettings {
override def apply(settings: MockSettings): MockSettings = settings
}

val lenientStubs: LeniencySettings = new LeniencySettings {
override def apply(settings: MockSettings): MockSettings = settings.lenient()
}
}

private[mockito] trait Verifications {

/**
Expand Down
23 changes: 23 additions & 0 deletions common/src/main/scala/org/mockito/mockito.scala
Original file line number Diff line number Diff line change
Expand Up @@ -613,4 +613,27 @@ package object mockito {
new Equality[T] with Serializable {
override def areEqual(a: T, b: Any): Boolean = Equality.default[T].areEqual(a, b)
}

/**
* Implicit [[org.mockito.LeniencySettings LeniencySettings]] provided here for convenience
*
* Neither are in implicit scope as is; pull one or the other in to activate the respective semantics, for
* example:
*
* {{{
* import org.mockito.leniency.lenient
*
* withObjectSpied[SomeObject.type] {
* SomeObject.getExternalThing returns "external-thing"
* SomeObject.getOtherThing returns "other-thing"
* SomeObject.getExternalThing should be("external-thing")
* }
* }}}
*
* Note: strict stubbing is active by default via [[org.mockito.LeniencySettings#strictStubs strictStubs]]
*/
object leniency {
implicit val strict: LeniencySettings = LeniencySettings.strictStubs
implicit val lenient: LeniencySettings = LeniencySettings.lenientStubs
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.mockito.{ ArgumentMatchersSugar, IdiomaticStubbing }
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import user.org.mockito.matchers.{ ValueCaseClassInt, ValueCaseClassString, ValueClass }

import scala.collection.parallel.immutable
import scala.concurrent.{ Await, Future }
import scala.util.Random
Expand Down Expand Up @@ -246,8 +245,8 @@ class IdiomaticStubbingTest extends AnyWordSpec with Matchers with ArgumentMatch
}
}

"doStub" should {
"stub a spy that would fail if the real impl is called" in {
"spy" should {
"stub a function that would fail if the real impl is called" in {
val aSpy = spy(new Org)

an[IllegalArgumentException] should be thrownBy {
Expand All @@ -264,7 +263,7 @@ class IdiomaticStubbingTest extends AnyWordSpec with Matchers with ArgumentMatch
}
}

"stub a spy with an answer" in {
"stub a function with an answer" in {
val aSpy = spy(new Org)

((i: Int) => i * 10 + 2) willBe answered by aSpy.doSomethingWithThisInt(*)
Expand Down Expand Up @@ -295,6 +294,49 @@ class IdiomaticStubbingTest extends AnyWordSpec with Matchers with ArgumentMatch
org.doSomethingWithThisIntAndStringAndBoolean(1, "2", v3 = true) shouldBe "not mocked"
org.doSomethingWithThisIntAndStringAndBoolean(1, "2", v3 = false) shouldBe ""
}

"stub an object method" in {
FooObject.simpleMethod shouldBe "not mocked!"

withObjectSpied[FooObject.type] {
FooObject.simpleMethod returns "spied!"
FooObject.simpleMethod shouldBe "spied!"
}

FooObject.simpleMethod shouldBe "not mocked!"
}

"call real object method when not stubbed" in {
val now = FooObject.stateDependantMethod
withObjectSpied[FooObject.type] {
FooObject.simpleMethod returns s"spied!"
FooObject.simpleMethod shouldBe s"spied!"
FooObject.stateDependantMethod shouldBe now
}
}

"be thread safe" when {
"always stubbing object methods" in {
immutable.ParSeq.range(1, 100).foreach { i =>
withObjectSpied[FooObject.type] {
FooObject.simpleMethod returns s"spied!-$i"
FooObject.simpleMethod shouldBe s"spied!-$i"
}
}
}

"intermittently stubbing object methods" in {
val now = FooObject.stateDependantMethod
immutable.ParSeq.range(1, 100).foreach { i =>
if (i % 2 == 0)
withObjectSpied[FooObject.type] {
FooObject.stateDependantMethod returns i
FooObject.stateDependantMethod shouldBe i
}
else FooObject.stateDependantMethod shouldBe now
}
}
}
}

"mock" should {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,5 +388,51 @@ class MockitoScalaSessionTest extends AnyWordSpec with IdiomaticMockito with Mat

thrown.getMessage should include("You have a NullPointerException here:")
}

"verify object spies" when {

"successfully for uncalled lenient stubs" in {
MockitoScalaSession().run {
import org.mockito.leniency.lenient

withObjectSpied[FooObject.type] {
FooObject.stateDependantMethod returns 1234L
FooObject.simpleMethod returns s"spied!"
FooObject.simpleMethod shouldBe s"spied!"
}
}
}

"unsuccessfully for uncalled strict stubs" in {
val thrown = the[UnnecessaryStubbingException] thrownBy {
MockitoScalaSession().run {
import org.mockito.leniency.strict

withObjectSpied[FooObject.type] {
FooObject.stateDependantMethod returns 1234L
FooObject.simpleMethod returns s"spied!"
FooObject.simpleMethod shouldBe s"spied!"
}
}
}

thrown.getMessage should include("Unnecessary stubbings detected")
}

"unsuccessfully by default (strict) for uncalled stubs" in {

val thrown = the[UnnecessaryStubbingException] thrownBy {
MockitoScalaSession().run {
withObjectSpied[FooObject.type] {
FooObject.stateDependantMethod returns 1234L
FooObject.simpleMethod returns s"spied!"
FooObject.simpleMethod shouldBe s"spied!"
}
}
}

thrown.getMessage should include("Unnecessary stubbings detected")
}
}
}
}