Skip to content

Commit b86d3b7

Browse files
committed
Add argMatching[T]
1 parent 8ed2cbc commit b86d3b7

File tree

5 files changed

+62
-11
lines changed

5 files changed

+62
-11
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ This trait exposes all the existent `org.mockito.ArgumentMatchers` but again it
9494
* `isNull` and `isNotNull` are deprecated as using nulls in Scala is clear code smell
9595
* Adds support for value classes via `anyVal[T]` and `eqToVal[T]()` **NOTE: both had been deprecated (use `any[T]` or `eqTo[T]` instead)**
9696
* Adds `function0` to easily match for a function that returns a given value
97+
* Adds `argMatching` that takes a partial function to match, i.e. `argMatching({ case Baz(_, "pepe") => })`
9798

9899
Again, the companion object also extends the trait to allow the usage of the API without mixing-in the trait in case that's desired
99100

common/src/main/scala/org/mockito/matchers/ThatMatchers.scala

+5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ private[mockito] trait ThatMatchers {
7575
*/
7676
def longThat(matcher: ArgumentMatcher[Long]): Long = argThat(matcher)
7777

78+
def argMatching[T](pf: PartialFunction[Any, Unit]) =
79+
argThat[T](new ArgumentMatcher[T] {
80+
override def matches(argument: T): Boolean = pf.isDefinedAt(argument)
81+
override def toString: String = "argMatching(...)"
82+
})
7883
}
7984

8085
private[mockito] object ThatMatchers extends ThatMatchers

core/src/test/scala/user/org/mockito/IdiomaticMockitoTest.scala

+23-7
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package user.org.mockito
33
import org.mockito.captor.ArgCaptor
44
import org.mockito.exceptions.verification._
55
import org.mockito.invocation.InvocationOnMock
6-
import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito}
7-
import org.scalatest.{Matchers, WordSpec}
8-
import user.org.mockito.matchers.{ValueCaseClass, ValueClass}
6+
import org.mockito.{ ArgumentMatchersSugar, IdiomaticMockito }
7+
import org.scalatest.{ Matchers, WordSpec }
8+
import user.org.mockito.matchers.{ ValueCaseClass, ValueClass }
99

1010
class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito with ArgumentMatchersSugar {
1111

@@ -36,12 +36,16 @@ class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito
3636
def valueCaseClass(n: Int, v: ValueCaseClass): String = ???
3737

3838
def returnsValueCaseClass: ValueCaseClass = ???
39+
40+
def baz(i: Int, b: Baz): String = ???
3941
}
4042

4143
class Bar {
4244
def iHaveDefaultArgs(v: String = "default"): String = v
4345
}
4446

47+
case class Baz(param1: Int, param2: String)
48+
4549
"StubbingOps" should {
4650
"stub a return value" in {
4751
val org = mock[Org]
@@ -119,8 +123,8 @@ class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito
119123
org.doSomethingWithThisInt(*) shouldAnswer ((i: Int) => i * 10 + 2)
120124
org.doSomethingWithThisIntAndString(*, *) shouldAnswer ((i: Int, s: String) => (i * 10 + s.toInt).toString)
121125
org.doSomethingWithThisIntAndStringAndBoolean(*, *, *) shouldAnswer ((i: Int,
122-
s: String,
123-
boolean: Boolean) => (i * 10 + s.toInt).toString + boolean)
126+
s: String,
127+
boolean: Boolean) => (i * 10 + s.toInt).toString + boolean)
124128

125129
org.doSomethingWithThisInt(4) shouldBe 42
126130
org.doSomethingWithThisIntAndString(4, "2") shouldBe "42"
@@ -404,7 +408,7 @@ class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito
404408
}
405409

406410
"work with a captor" in {
407-
val org = mock[Org]
411+
val org = mock[Org]
408412
val argCaptor = ArgCaptor[Int]
409413

410414
org.doSomethingWithThisIntAndString(42, "test")
@@ -441,7 +445,7 @@ class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito
441445

442446
"mix arguments and raw parameters" should {
443447
"create a mock where I can mix matchers, normal and implicit parameters" in {
444-
val org = mock[Org]
448+
val org = mock[Org]
445449
implicit val implicitValue: Implicit[Int] = mock[Implicit[Int]]
446450

447451
org.iHaveTypeParamsAndImplicits[Int, String](*, "test") shouldReturn "mocked!"
@@ -505,6 +509,18 @@ class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito
505509
org.valueCaseClass(*, ValueCaseClass(200)) was called
506510
}
507511

512+
"argMatching works with new syntax" in {
513+
val org = mock[Org]
514+
515+
org.baz(2, argMatching({ case Baz(n, _) if n > 90 => })) shouldReturn "mocked!"
516+
org.baz(2, Baz(100, "pepe")) shouldBe "mocked!"
517+
org.baz(2, argMatching({ case Baz(_, "pepe") => })) was called
518+
519+
an[WantedButNotInvoked] should be thrownBy {
520+
org.baz(2, argMatching({ case Baz(99, "pepe") => })) was called
521+
}
522+
}
523+
508524
"anyVal works with new syntax" in {
509525
val org = mock[Org]
510526

core/src/test/scala/user/org/mockito/matchers/ThatMatchersTest.scala

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package user.org.mockito.matchers
22

3-
import org.mockito.{ArgumentMatcher, ArgumentMatchersSugar, MockitoSugar}
4-
import org.scalatest.{FlatSpec, Matchers => ScalaTestMatchers}
5-
6-
class ThatMatchersTest extends FlatSpec with MockitoSugar with ScalaTestMatchers with ArgumentMatchersSugar {
3+
import org.mockito.exceptions.verification.WantedButNotInvoked
4+
import org.mockito.{ ArgumentMatcher, ArgumentMatchersSugar, MockitoSugar }
5+
import org.scalatest.{ FlatSpec, Matchers => ScalaTestMatchers }
76

7+
object ThatMatchersTest {
88
class EqTo[T](value: T) extends ArgumentMatcher[T] {
99
override def matches(argument: T): Boolean = argument == value
1010
}
@@ -34,6 +34,34 @@ class ThatMatchersTest extends FlatSpec with MockitoSugar with ScalaTestMatchers
3434

3535
def baz(v: Baz): Baz = v
3636
}
37+
}
38+
39+
class ThatMatchersTest extends FlatSpec with MockitoSugar with ScalaTestMatchers with ArgumentMatchersSugar {
40+
41+
import ThatMatchersTest._
42+
43+
"argMatching[T]" should "work in various scenarios" in {
44+
val aMock = mock[Foo]
45+
46+
aMock.bar("meh")
47+
verify(aMock).bar(argMatching({ case "meh" => }))
48+
49+
aMock.barTyped("meh")
50+
verify(aMock).barTyped(argMatching({ case "meh" => }))
51+
52+
aMock.bar(List("meh"))
53+
verify(aMock).bar(argMatching({ case "meh" :: Nil => }))
54+
55+
aMock.baz(Baz("Hello", "World"))
56+
verify(aMock).baz(argMatching({ case Baz("Hello", "World") => }))
57+
verify(aMock).baz(argMatching({ case Baz(_, "World") => }))
58+
verify(aMock).baz(argMatching({ case Baz("Hello", _) => }))
59+
verify(aMock).baz(argMatching({ case Baz(_, _) => }))
60+
61+
an[WantedButNotInvoked] should be thrownBy {
62+
verify(aMock).baz(argMatching({ case Baz("", _) => }))
63+
}
64+
}
3765

3866
"argThat[T]" should "work with AnyRef" in {
3967
val aMock = mock[Foo]

macro/src/main/scala/org/mockito/Utils.scala

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ object Utils {
5252
case q"$_.floatThat[$_]($_)" => true
5353
case q"$_.shortThat[$_]($_)" => true
5454
case q"$_.longThat[$_]($_)" => true
55+
case q"$_.argMatching[$_]($_)" => true
5556

5657
case q"$_.n.>[$_]($_)($_)" => true
5758
case q"$_.n.>=[$_]($_)($_)" => true

0 commit comments

Comments
 (0)