diff --git a/.gitignore b/.gitignore index fa7d7be7..fe2a96b7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target/ dist/ .gradle/ build/ +*.sc \ No newline at end of file diff --git a/README.md b/README.md index 1c4ed16a..8c126450 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ The most popular mocking framework for Java, now in Scala!!! [![Download](https://api.bintray.com/packages/mockito/maven/mockito-scala/images/download.svg) ](https://bintray.com/mockito/maven/mockito-scala/_latestVersion) [![Maven Central](https://img.shields.io/maven-central/v/org.mockito/mockito-scala_2.12.svg)](https://search.maven.org/search?q=mockito-scala) + +[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/mockito-scala/) ## Why separate project? The library has independent developers, release cycle and versioning from core mockito library (https://github.com/mockito/mockito). This is intentional because core Mockito developers don't use Scala and cannot confidently review PRs, and set the vision for the Scala library. diff --git a/build.sbt b/build.sbt index 82544f5b..cc91b533 100644 --- a/build.sbt +++ b/build.sbt @@ -32,13 +32,14 @@ lazy val commonSettings = lazy val commonLibraries = Seq( "org.mockito" % "mockito-core" % "2.21.0", - "org.scalatest" %% "scalatest" % "3.0.5" % "provided" + "org.scalactic" %% "scalactic" % "3.0.5", + "org.scalatest" %% "scalatest" % "3.0.5" % "provided", ) lazy val common = (project in file("common")) .settings( commonSettings, - libraryDependencies += "org.mockito" % "mockito-core" % "2.21.0", + libraryDependencies ++= commonLibraries, libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, publish := {}, publishLocal := {}, diff --git a/build.sh b/build.sh index ac647e8f..9f5487ec 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -XX:+CMSClassUnloadingEnabled -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish' \ No newline at end of file +java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -XX:+CMSClassUnloadingEnabled -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean +test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish' \ No newline at end of file diff --git a/common/src/main/scala/org/mockito/matchers/EqMatchers.scala b/common/src/main/scala/org/mockito/matchers/EqMatchers.scala index 2689a795..5e76c15c 100644 --- a/common/src/main/scala/org/mockito/matchers/EqMatchers.scala +++ b/common/src/main/scala/org/mockito/matchers/EqMatchers.scala @@ -1,18 +1,21 @@ package org.mockito package matchers -import org.mockito.{ ArgumentMatchers => JavaMatchers } +import org.mockito.{ArgumentMatchers => JavaMatchers} +import org.scalactic.Equality import scala.reflect.ClassTag private[mockito] trait EqMatchers { /** - * Delegates to ArgumentMatchers.eq(), it renames the method to eqTo to - * avoid clashes with the Scala eq method used for reference equality - * + * Creates a matcher that delegates on {{org.scalactic.Equality}} so you can always customise how the values are compared */ - def eqTo[T](value: T): T = JavaMatchers.eq(value) + def eqTo[T](value: T)(implicit $eq: Equality[T]): T = + ThatMatchers.argThat(new ArgumentMatcher[T] { + override def matches(v: T): Boolean = $eq.areEqual(v, value) + override def toString: String = s"eqTo($value)" + }) /** * Delegates to ArgumentMatchers.same(), it's only here so we expose all the `ArgumentMatchers` diff --git a/common/src/main/scala/org/mockito/matchers/NumericMatchers.scala b/common/src/main/scala/org/mockito/matchers/NumericMatchers.scala index e0967c5e..90cf7735 100644 --- a/common/src/main/scala/org/mockito/matchers/NumericMatchers.scala +++ b/common/src/main/scala/org/mockito/matchers/NumericMatchers.scala @@ -1,6 +1,7 @@ package org.mockito.matchers import org.mockito.ArgumentMatcher +import org.scalactic.TripleEqualsSupport.Spread /** * I transform everything to BigDecimal so any kind of number type can be compared @@ -15,13 +16,55 @@ class N { import ThatMatchers.argThat + /** + * Creates a matcher that works only if there is a Numeric[T] associated with the type, this allows you to write stuff like + * + * aMock.pepe(4.1) + * aMock.pepe(n > 4) was called + * + */ def >[N: Numeric](n: N): N = argThat[N](new NumericMatcher(n, ">", _ > _)) + /** + * Creates a matcher that works only if there is a Numeric[T] associated with the type, this allows you to write stuff like + * + * aMock.pepe(4) + * aMock.pepe(n >= 4) was called + * + */ def >=[N: Numeric](n: N): N = argThat[N](new NumericMatcher(n, ">=", _ >= _)) + /** + * Creates a matcher that works only if there is a Numeric[T] associated with the type, this allows you to write stuff like + * + * aMock.pepe(3.1) + * aMock.pepe(n < 4) was called + * + */ def <[N: Numeric](n: N): N = argThat[N](new NumericMatcher(n, "<", _ < _)) + /** + * Creates a matcher that works only if there is a Numeric[T] associated with the type, this allows you to write stuff like + * + * aMock.pepe(4) + * aMock.pepe(n <= 4) was called + * + */ def <=[N: Numeric](n: N): N = argThat[N](new NumericMatcher(n, "<=", _ <= _)) + + /** + * Creates a matcher that delegates on {{org.scalactic.TripleEqualsSupport.Spread}} so you can get around the lack of + * precision on floating points, e.g. + * + * aMock.barDouble(4.999) + * verify(aMock).barDouble(=~(5.0 +- 0.001)) + * + */ + def =~[T](spread: Spread[T]): T = + ThatMatchers.argThat(new ArgumentMatcher[T] { + override def matches(v: T): Boolean = spread.isWithin(v) + override def toString: String = s"=~($spread)" + }) } private[mockito] trait NumericMatchers { diff --git a/core/src/test/scala/user/org/mockito/matchers/EqMatchersTest.scala b/core/src/test/scala/user/org/mockito/matchers/EqMatchersTest.scala index b709784b..dec3f815 100644 --- a/core/src/test/scala/user/org/mockito/matchers/EqMatchersTest.scala +++ b/core/src/test/scala/user/org/mockito/matchers/EqMatchersTest.scala @@ -1,6 +1,8 @@ package user.org.mockito.matchers import org.mockito.{ArgumentMatchersSugar, MockitoSugar} +import org.mockito.exceptions.verification.WantedButNotInvoked +import org.scalactic.{Equality, StringNormalizations} import org.scalatest.{FlatSpec, Matchers => ScalaTestMatchers} class EqMatchersTest extends FlatSpec with MockitoSugar with ScalaTestMatchers with ArgumentMatchersSugar { @@ -151,4 +153,15 @@ class EqMatchersTest extends FlatSpec with MockitoSugar with ScalaTestMatchers w verify(aMock).baz(refEq(Baz("Hello", "World"))) verify(aMock).baz(refEq(Baz("Hello", "Mars"), "param2")) } + + "eqTo[T]" should "work when an implicit Equality is in scope" in { + import StringNormalizations._ + + implicit val eq: Equality[String] = decided by defaultEquality[String] afterBeing lowerCased + + val aMock = mock[Foo] + + aMock.bar("meh") + verify(aMock).bar(eqTo("MEH")) + } } diff --git a/core/src/test/scala/user/org/mockito/matchers/NumberMatchersTest.scala b/core/src/test/scala/user/org/mockito/matchers/NumericMatchersTest.scala similarity index 74% rename from core/src/test/scala/user/org/mockito/matchers/NumberMatchersTest.scala rename to core/src/test/scala/user/org/mockito/matchers/NumericMatchersTest.scala index 424897e2..1643c50a 100644 --- a/core/src/test/scala/user/org/mockito/matchers/NumberMatchersTest.scala +++ b/core/src/test/scala/user/org/mockito/matchers/NumericMatchersTest.scala @@ -1,12 +1,12 @@ package user.org.mockito.matchers -import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito} +import org.mockito.{ ArgumentMatchersSugar, IdiomaticMockito } import org.mockito.exceptions.verification.WantedButNotInvoked -import org.scalatest.{FlatSpec, Matchers} +import org.scalatest.{ FlatSpec, Matchers } -class NumberMatchersTest extends FlatSpec with IdiomaticMockito with Matchers with ArgumentMatchersSugar { +class NumericMatchersTest extends FlatSpec with IdiomaticMockito with Matchers with ArgumentMatchersSugar { - trait Foo { + class Foo { def pepe[N](n: N, v: String = "meh"): N = ??? } @@ -69,4 +69,16 @@ class NumberMatchersTest extends FlatSpec with IdiomaticMockito with Matchers wi aMock.pepe(n <= 3.0) was called } } + + "=~" should "work" in { + val aMock = mock[Foo] + + aMock.pepe(4.999) + + aMock.pepe(n =~ 5.0 +- 0.001) was called + + an[WantedButNotInvoked] shouldBe thrownBy { + aMock.pepe(n =~ 5.0 +- 0.00001) was called + } + } } diff --git a/macro/src/main/scala/org/mockito/Utils.scala b/macro/src/main/scala/org/mockito/Utils.scala index b9bec99a..e42ee7ed 100644 --- a/macro/src/main/scala/org/mockito/Utils.scala +++ b/macro/src/main/scala/org/mockito/Utils.scala @@ -55,6 +55,7 @@ object Utils { case q"$_.n.>=[$_]($_)($_)" => true case q"$_.n.<[$_]($_)($_)" => true case q"$_.n.<=[$_]($_)($_)" => true + case q"$_.n.=~[$_]($_)" => true case q"$_.Captor.asCapture[$_]($_)" => true