Skip to content

Commit aaa1fd9

Browse files
authored
Merge pull request #56 from mockito/refactor-value-matchers
Refactor value matchers and postFix dependent syntax
2 parents b5911e3 + 91c82a1 commit aaa1fd9

File tree

11 files changed

+138
-96
lines changed

11 files changed

+138
-96
lines changed

README.md

+16-7
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,29 @@ The library has independent developers, release cycle and versioning from core m
3636
* The usage of `org.mockito.Answer[T]` was removed from the API in favour of [Function Answers](#function-answers)
3737
* If you were using something like `doAnswer(_ => <something>).when ...` to lazily compute a return value when the method is actually called you should now write it like `doAnswer(<something>).when ...`, no need of passing a function as that argument is by-name
3838
* If you have chained return values like `when(myMock.foo) thenReturn "a" thenReturn "b" etc...` the syntax has changed a bit to `when(myMock.foo) thenReturn "a" andThen "b" etc...`
39-
* Idiomatic syntax has some changes to allow support of mixing values and argument matchers [Mix-and-Match](#mix-and-match)
39+
* Idiomatic syntax has some changes to remove postFix operations and also allow support for mixing values and argument matchers [Mix-and-Match](#mix-and-match)
4040
```scala
41+
aMock.bar shouldCallRealMethod => aMock.bar shouldCall realMethod
42+
4143
aMock wasCalled on bar => aMock.bar was called
4244
aMock wasCalled onlyOn bar => aMock.bar wasCalled onlyHere
43-
aMock was never called on bar => aMock.bar was never called
45+
aMock was never called on bar => aMock.bar wasNever called
4446
aMock wasCalled twiceOn bar => aMock.bar wasCalled twice
4547
aMock wasCalled sixTimesOn bar => aMock.bar wasCalled sixTimes
48+
aMock was never called => aMock.bar wasNever called
49+
aMock was never called again => aMock.bar wasNever calledAgain
4650

4751
"mocked!" willBe returned by aMock bar => "mocked!" willBe returned by aMock.bar
4852
"mocked!" willBe answered by aMock bar => "mocked!" willBe answered by aMock.bar
4953
((i: Int) => i * 10) willBe answered by aMock bar * => ((i: Int) => i * 10) willBe answered by aMock.bar(*)
5054
theRealMethod willBe called by aMock bar => theRealMethod willBe called by aMock.bar
5155
new IllegalArgumentException willBe thrown by aMock bar => new IllegalArgumentException willBe thrown by aMock.bar
5256
```
57+
* eqToVal matcher syntax was improved to look more natural [Value Class Matchers](#value-class-matchers)
58+
```scala
59+
verify(myObj).myMethod(eqToVal[MyValueClass](456)) => verify(myObj).myMethod(eqToVal(MyValueClass(456)))
60+
myObj.myMethod(eqToVal[MyValueClass](456)) was called => myObj.myMethod(eqToVal(MyValueClass(456))) was called
61+
```
5362

5463
## Getting started
5564

@@ -94,7 +103,7 @@ when(myObj.myMethod(anyVal[MyValueClass]) thenReturn "something"
94103

95104
myObj.myMethod(MyValueClass(456)) shouldBe "something"
96105

97-
verify(myObj).myMethod(eqToVal[MyValueClass](456))
106+
verify(myObj).myMethod(eqToVal(MyValueClass(456)))
98107
```
99108

100109
## Improved ArgumentCaptor
@@ -240,7 +249,7 @@ val aMock = mock[Foo]
240249

241250
when(aMock.bar) thenReturn "mocked!" <=> aMock.bar shouldReturn "mocked!"
242251
when(aMock.bar) thenReturn "mocked!" thenReturn "mocked again!" <=> aMock.bar shouldReturn "mocked!" andThen "mocked again!"
243-
when(aMock.bar) thenCallRealMethod() <=> aMock.bar shouldCallRealMethod
252+
when(aMock.bar) thenCallRealMethod() <=> aMock.bar shouldCall realMethod
244253
when(aMock.bar).thenThrow[IllegalArgumentException] <=> aMock.bar.shouldThrow[IllegalArgumentException]
245254
when(aMock.bar) thenThrow new IllegalArgumentException <=> aMock.bar shouldThrow new IllegalArgumentException
246255
when(aMock.bar) thenAnswer(_ => "mocked!") <=> aMock.bar shouldAnswer "mocked!"
@@ -252,14 +261,14 @@ doAnswer(_.getArgument[Int](0) * 10).when(aMock).bar(any) <=> ((i: Int) =>
252261
doCallRealMethod.when(aMock).bar <=> theRealMethod willBe called by aMock.bar
253262
doThrow(new IllegalArgumentException).when(aMock).bar <=> new IllegalArgumentException willBe thrown by aMock.bar
254263

255-
verifyZeroInteractions(aMock) <=> aMock was never called
264+
verifyZeroInteractions(aMock) <=> aMock wasNever called
256265
verify(aMock).bar <=> aMock.bar was called
257266
verify(aMock).bar(any) <=> aMock.bar(*) was called
258267
verify(aMock, only).bar <=> aMock.bar wasCalled onlyHere
259-
verify(aMock, never).bar <=> aMock.bar was never called
268+
verify(aMock, never).bar <=> aMock.bar wasNever called
260269
verify(aMock, times(2)).bar <=> aMock.bar wasCalled twice
261270
verify(aMock, times(6)).bar <=> aMock.bar wasCalled sixTimes
262-
verifyNoMoreInteractions(aMock) <=> aMock was never called again
271+
verifyNoMoreInteractions(aMock) <=> aMock wasNever calledAgain
263272

264273
val order = inOrder(mock1, mock2) <=> InOrder(mock1, mock2) { implicit order =>
265274
order.verify(mock2).someMethod() <=> mock2.someMethod() was called

core/src/main/scala/org/mockito/IdiomaticMockito.scala

+10-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package org.mockito
22

33
import org.mockito.stubbing.{ DefaultAnswer, ScalaOngoingStubbing }
44
import org.mockito.MockitoSugar._
5-
import org.mockito.VerifyMacro.{ AtLeast, AtMost, OnlyOn, Times }
5+
import org.mockito.VerifyMacro._
66
import org.mockito.WhenMacro._
77

88
import scala.language.experimental.macros
@@ -30,15 +30,17 @@ trait IdiomaticMockito extends MockCreator {
3030

3131
def shouldReturn: ReturnActions[T] = macro WhenMacro.shouldReturn[T]
3232

33-
def shouldCallRealMethod: ScalaOngoingStubbing[T] = macro WhenMacro.shouldCallRealMethod[T]
33+
def shouldCall(crm: RealMethod): ScalaOngoingStubbing[T] = macro WhenMacro.shouldCallRealMethod[T]
3434

3535
def shouldThrow: ThrowActions[T] = macro WhenMacro.shouldThrow[T]
3636

3737
def shouldAnswer: AnswerActions[T] = macro WhenMacro.shouldAnswer[T]
3838

3939
def was(called: Called.type)(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasMacro[T]
4040

41-
def was(n: Never): NeverInstance[T] = new NeverInstance(stubbing)
41+
def wasNever(called: Called.type)(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasNotMacro[T]
42+
43+
def wasNever(called: CalledAgain)(implicit $ev: T <:< AnyRef): Unit = verifyNoMoreInteractions(stubbing.asInstanceOf[AnyRef])
4244

4345
def wasCalled(t: Times)(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasMacroTimes[T]
4446

@@ -86,17 +88,14 @@ trait IdiomaticMockito extends MockCreator {
8688

8789
class On
8890
class Never
89-
//noinspection UnitMethodIsParameterless
90-
class NeverInstance[T](mock: => T) {
91-
def called(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasNotMacro[T]
92-
def called(again: Again)(implicit $ev: T <:< AnyRef): Unit = verifyNoMoreInteractions(mock.asInstanceOf[AnyRef])
93-
}
94-
class Again
91+
class CalledAgain
92+
93+
val calledAgain = new CalledAgain
94+
95+
val realMethod = new RealMethod
9596

9697
val on = new On
9798
val onlyHere = new OnlyOn
98-
val never = new Never
99-
val again = new Again
10099
val once = Times(1)
101100
val twice = Times(2)
102101
val thrice = Times(3)
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package org.mockito.matchers
22

3+
import scala.language.experimental.macros
4+
35
trait MacroBasedMatchers {
46

57
/**
6-
* Wraps the standard 'ArgumentMatchers.eq()' matcher on the value class provided, this one requires the type to be explicit
7-
*/
8-
def eqToVal[T](value: Any)(implicit valueClassMatchers: ValueClassMatchers[T]): T = valueClassMatchers.eqToVal(value)
8+
* To be used instead of eqTo when the argument is a value class
9+
*/
10+
def eqToVal[T](value: T): T = macro ValueClassMatchers.eqToValMatcher[T]
911

1012
/**
11-
* Wraps the standard 'any' matcher on the value class provided, this one requires the type to be explicit
13+
* To be used instead of any when the argument is a value class
1214
*/
13-
def anyVal[T](implicit valueClassMatchers: ValueClassMatchers[T]): T = valueClassMatchers.anyVal
15+
def anyVal[T]: T = macro ValueClassMatchers.anyValMatcher[T]
1416

1517
}

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

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

3-
import org.mockito.{ArgumentMatchersSugar, MockitoSugar}
3+
import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
44
import org.mockito.invocation.InvocationOnMock
5-
import org.scalatest.{WordSpec, Matchers => ScalaTestMatchers}
6-
7-
import scala.language.postfixOps
5+
import org.scalatest.{ WordSpec, Matchers => ScalaTestMatchers }
86

97
class DoSomethingTest extends WordSpec with MockitoSugar with ScalaTestMatchers with ArgumentMatchersSugar {
108

@@ -54,15 +52,15 @@ class DoSomethingTest extends WordSpec with MockitoSugar with ScalaTestMatchers
5452
"simplify answer API" in {
5553
val aMock = mock[Foo]
5654

57-
doAnswer((i: Int, s: String) => i * 10 + s.toInt toString).when(aMock).doSomethingWithThisIntAndString(*, *)
55+
doAnswer((i: Int, s: String) => (i * 10 + s.toInt).toString).when(aMock).doSomethingWithThisIntAndString(*, *)
5856

5957
aMock.doSomethingWithThisIntAndString(4, "2") shouldBe "42"
6058
}
6159

6260
"simplify answer API (invocation usage)" in {
6361
val aMock = mock[Foo]
6462

65-
doAnswer((i: InvocationOnMock) => i.getArgument[Int](0) * 10 + i.getArgument[String](1).toInt toString)
63+
doAnswer((i: InvocationOnMock) => (i.getArgument[Int](0) * 10 + i.getArgument[String](1).toInt).toString)
6664
.when(aMock)
6765
.doSomethingWithThisIntAndString(*, *)
6866

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

+56-21
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package user.org.mockito
22

3-
import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito, VerifyOrder}
43
import org.mockito.captor.ArgCaptor
54
import org.mockito.exceptions.verification._
65
import org.mockito.invocation.InvocationOnMock
6+
import org.mockito.{ ArgumentMatchersSugar, IdiomaticMockito }
77
import org.scalatest
88
import org.scalatest.WordSpec
9-
10-
import scala.language.postfixOps
9+
import user.org.mockito.matchers.{ ValueCaseClass, ValueClass }
1110

1211
class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with IdiomaticMockito with ArgumentMatchersSugar {
1312

@@ -27,11 +26,15 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
2726

2827
def highOrderFunction(f: Int => String): String = "not mocked"
2928

30-
def iReturnAFunction(v: Int): Int => String = i => i * v toString
29+
def iReturnAFunction(v: Int): Int => String = i => (i * v).toString
3130

3231
def iBlowUp(v: Int, v2: String): String = throw new IllegalArgumentException("I was called!")
3332

3433
def iHaveTypeParamsAndImplicits[A, B](a: A, b: B)(implicit v3: Implicit[A]): String = "not mocked"
34+
35+
def valueClass(n: Int, v: ValueClass): String = ???
36+
37+
def valueCaseClass(n: Int, v: ValueCaseClass): String = ???
3538
}
3639

3740
class Bar {
@@ -58,7 +61,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
5861
}
5962

6063
"create a mock where I can mix matchers, normal and implicit parameters" in {
61-
val aMock = mock[Foo]
64+
val aMock = mock[Foo]
6265
implicit val implicitValue: Implicit[Int] = mock[Implicit[Int]]
6366

6467
aMock.iHaveTypeParamsAndImplicits[Int, String](*, "test") shouldReturn "mocked!"
@@ -73,7 +76,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
7376
"stub a real call" in {
7477
val aMock = mock[Foo]
7578

76-
aMock.bar shouldCallRealMethod
79+
aMock.bar shouldCall realMethod
7780

7881
aMock.bar shouldBe "not mocked"
7982
}
@@ -117,10 +120,10 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
117120
val aMock = mock[Foo]
118121

119122
aMock.doSomethingWithThisInt(*) shouldAnswer ((i: Int) => i * 10 + 2)
120-
aMock.doSomethingWithThisIntAndString(*, *) shouldAnswer ((i: Int, s: String) => i * 10 + s.toInt toString)
123+
aMock.doSomethingWithThisIntAndString(*, *) shouldAnswer ((i: Int, s: String) => (i * 10 + s.toInt).toString)
121124
aMock.doSomethingWithThisIntAndStringAndBoolean(*, *, *) shouldAnswer ((i: Int,
122125
s: String,
123-
boolean: Boolean) => (i * 10 + s.toInt toString) + boolean)
126+
boolean: Boolean) => (i * 10 + s.toInt).toString + boolean)
124127

125128
aMock.doSomethingWithThisInt(4) shouldBe 42
126129
aMock.doSomethingWithThisIntAndString(4, "2") shouldBe "42"
@@ -167,7 +170,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
167170
"allow using less params than method on answer stubbing" in {
168171
val aMock = mock[Foo]
169172

170-
aMock.doSomethingWithThisIntAndStringAndBoolean(*, *, *) shouldAnswer ((i: Int, s: String) => i * 10 + s.toInt toString)
173+
aMock.doSomethingWithThisIntAndStringAndBoolean(*, *, *) shouldAnswer ((i: Int, s: String) => (i * 10 + s.toInt).toString)
171174

172175
aMock.doSomethingWithThisIntAndStringAndBoolean(4, "2", v3 = true) shouldBe "42"
173176
}
@@ -192,7 +195,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
192195
"stub a method that returns a function" in {
193196
val aMock = mock[Foo]
194197

195-
aMock.iReturnAFunction(*) shouldReturn (_.toString) andThen (i => (i * 2) toString) andThenCallRealMethod ()
198+
aMock.iReturnAFunction(*) shouldReturn (_.toString) andThen (i => (i * 2).toString) andThenCallRealMethod ()
196199

197200
aMock.iReturnAFunction(0)(42) shouldBe "42"
198201
aMock.iReturnAFunction(0)(42) shouldBe "84"
@@ -222,8 +225,8 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
222225
val aSpy = spy(new Foo)
223226

224227
((i: Int) => i * 10 + 2) willBe answered by aSpy.doSomethingWithThisInt(*)
225-
((i: Int, s: String) => i * 10 + s.toInt toString) willBe answered by aSpy.doSomethingWithThisIntAndString(*, *)
226-
((i: Int, s: String, boolean: Boolean) => (i * 10 + s.toInt toString) + boolean) willBe answered by aSpy
228+
((i: Int, s: String) => (i * 10 + s.toInt).toString) willBe answered by aSpy.doSomethingWithThisIntAndString(*, *)
229+
((i: Int, s: String, boolean: Boolean) => (i * 10 + s.toInt).toString + boolean) willBe answered by aSpy
227230
.doSomethingWithThisIntAndStringAndBoolean(*, *, v3 = true)
228231
(() => "mocked!") willBe answered by aSpy.bar
229232
"mocked!" willBe answered by aSpy.baz
@@ -265,12 +268,13 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
265268
"check a mock was not used" in {
266269
val aMock = mock[Foo]
267270

268-
aMock was never called
271+
aMock wasNever called
272+
aMock wasNever called
269273

270274
a[NoInteractionsWanted] should be thrownBy {
271275
aMock.baz
272276

273-
aMock was never called
277+
aMock wasNever called
274278
}
275279
}
276280

@@ -279,12 +283,12 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
279283
}
280284

281285
"check a mock was not used (with setup)" in new SetupNeverUsed {
282-
aMock was never called
286+
aMock wasNever called
283287

284288
a[NoInteractionsWanted] should be thrownBy {
285289
aMock.baz
286290

287-
aMock was never called
291+
aMock wasNever called
288292
}
289293
}
290294

@@ -314,15 +318,15 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
314318
}
315319
}
316320

317-
"check a method was never called" in {
321+
"check a method wasNever called" in {
318322
val aMock = mock[Foo]
319323

320-
aMock.doSomethingWithThisIntAndString(*, "test") was never called
324+
aMock.doSomethingWithThisIntAndString(*, "test") wasNever called
321325

322326
a[NeverWantedButInvoked] should be thrownBy {
323327
aMock.doSomethingWithThisIntAndString(1, "test")
324328

325-
aMock.doSomethingWithThisIntAndString(*, "test") was never called
329+
aMock.doSomethingWithThisIntAndString(*, "test") wasNever called
326330
}
327331
}
328332

@@ -385,12 +389,12 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
385389

386390
aMock.bar was called
387391

388-
aMock was never called again
392+
aMock wasNever calledAgain
389393

390394
a[NoInteractionsWanted] should be thrownBy {
391395
aMock.bar
392396

393-
aMock was never called again
397+
aMock wasNever calledAgain
394398
}
395399
}
396400

@@ -429,4 +433,35 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
429433
}
430434
}
431435
}
436+
437+
"value class matchers" should {
438+
"eqToVal works with new syntax" in {
439+
val aMock = mock[Foo]
440+
441+
aMock.valueClass(1, eqToVal(new ValueClass("meh"))) shouldReturn "mocked!"
442+
aMock.valueClass(1, new ValueClass("meh")) shouldBe "mocked!"
443+
aMock.valueClass(1, eqToVal(new ValueClass("meh"))) was called
444+
445+
aMock.valueCaseClass(2, eqToVal(ValueCaseClass(100))) shouldReturn "mocked!"
446+
aMock.valueCaseClass(2, ValueCaseClass(100)) shouldBe "mocked!"
447+
aMock.valueCaseClass(2, eqToVal(ValueCaseClass(100))) was called
448+
449+
val value = ValueCaseClass(100)
450+
aMock.valueCaseClass(3, eqToVal(value)) shouldReturn "mocked!"
451+
aMock.valueCaseClass(3, ValueCaseClass(100)) shouldBe "mocked!"
452+
aMock.valueCaseClass(3, eqToVal(value)) was called
453+
}
454+
455+
"anyVal works with new syntax" in {
456+
val aMock = mock[Foo]
457+
458+
aMock.valueClass(1, anyVal[ValueClass]) shouldReturn "mocked!"
459+
aMock.valueClass(1, new ValueClass("meh")) shouldBe "mocked!"
460+
aMock.valueClass(1, anyVal[ValueClass]) was called
461+
462+
aMock.valueCaseClass(2, anyVal[ValueCaseClass]) shouldReturn "mocked!"
463+
aMock.valueCaseClass(2, ValueCaseClass(100)) shouldBe "mocked!"
464+
aMock.valueCaseClass(2, anyVal[ValueCaseClass]) was called
465+
}
466+
}
432467
}

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

+4-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package user.org.mockito
22

33
import org.mockito.captor.ArgCaptor
44
import org.mockito.invocation.InvocationOnMock
5-
import org.mockito.stubbing.{CallsRealMethods, DefaultAnswer}
6-
import org.mockito.{ArgumentMatchersSugar, MockitoSugar}
7-
import org.scalatest.{Matchers, WordSpec}
8-
9-
import scala.language.postfixOps
5+
import org.mockito.stubbing.{ CallsRealMethods, DefaultAnswer }
6+
import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
7+
import org.scalatest.{ Matchers, WordSpec }
108

119
//noinspection RedundantDefaultArgument
1210
class MockitoSugarTest extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar {
@@ -65,7 +63,7 @@ class MockitoSugarTest extends WordSpec with MockitoSugar with Matchers with Arg
6563
"create a mock with nice answer API (multiple params)" in {
6664
val aMock = mock[Foo]
6765

68-
when(aMock.doSomethingWithThisIntAndString(*,*)) thenAnswer ((i: Int, s: String) => i * 10 + s.toInt toString)
66+
when(aMock.doSomethingWithThisIntAndString(*, *)) thenAnswer ((i: Int, s: String) => (i * 10 + s.toInt).toString)
6967

7068
aMock.doSomethingWithThisIntAndString(4, "2") shouldBe "42"
7169
}

0 commit comments

Comments
 (0)