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!!!
[ ](https://bintray.com/mockito/maven/mockito-scala/_latestVersion)
[](https://search.maven.org/search?q=mockito-scala)
+
+[](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