Skip to content

Commit b36ee18

Browse files
authored
Merge pull request #24 from mockito/advanced-syntax
IdiomaticMockito
2 parents d853add + e838942 commit b36ee18

File tree

7 files changed

+444
-11
lines changed

7 files changed

+444
-11
lines changed

README.md

+45-5
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,12 @@ The library has independent developers, release cycle and versioning from core m
2323
* Repositories: [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cmockito-scala_2.12) or [JFrog's Bintray](https://bintray.com/mockito/maven/mockito-scala)
2424

2525

26-
For a more detailed explanation of the features please read [this](https://medium.com/@bbonanno_83496/introduction-to-mockito-scala-ede30769cbda) article series
27-
28-
2926
## Getting started
3027

31-
Then mixin one (or both) of the following traits as required
32-
3328
## `org.mockito.MockitoSugar`
3429

30+
For a more detailed explanation read [this](https://medium.com/@bbonanno_83496/introduction-to-mockito-scala-ede30769cbda)
31+
3532
This trait wraps the API available on `org.mockito.Mockito` from the Java version, but it provides a more Scala-like syntax, mainly
3633
* Fixes the compiler errors that sometimes occurred when using overloaded methods that use varargs like doReturn
3734
* Eliminates the need to use `classOf[T]`
@@ -50,6 +47,8 @@ The companion object also extends the trait to allow the usage of the API withou
5047

5148
## `org.mockito.ArgumentMatchersSugar`
5249

50+
For a more detailed explanation read [this](https://medium.com/@bbonanno_83496/introduction-to-mockito-scala-part-2-ba1a79cc4c53)
51+
5352
This trait exposes all the existent `org.mockito.ArgumentMatchers` but again it gives them a more Scala-like syntax, mainly
5453
* `eq` was renamed to `eqTo` to avoid clashing with the Scala `eq` operator for identity equality
5554
* `any` resolves to the correct type most of the times, removing the need of using the likes of `anyString`, `anyInt`, etc
@@ -72,6 +71,8 @@ verify(myObj).myMethod(eqToVal[MyValueClass](456))
7271

7372
## Improved ArgumentCaptor
7473

74+
For a more detailed explanation read [this](https://medium.com/@bbonanno_83496/introduction-to-mockito-scala-part-3-383c3b2ed55f)
75+
7576
A new set of classes were added to make it easier, cleaner and more elegant to work with ArgumentCaptors, they also add
7677
support to capture value classes without any annoying syntax
7778

@@ -124,6 +125,8 @@ session.finishMocking()
124125

125126
## `org.mockito.integrations.scalatest.MockitoFixture`
126127

128+
For a more detailed explanation read [this](https://medium.com/@bbonanno_83496/introduction-to-mockito-scala-part-3-383c3b2ed55f)
129+
127130
If you mix-in this trait on your test class **after** your favourite Spec trait, you will get an automatic
128131
`MockitoScalaSession` around each one of your tests, so **all** of them will run in **Strict Stub** mode.
129132

@@ -154,6 +157,43 @@ The main advantage being we don't have to remember to reset each one of the mock
154157
If for some reason we want to have a mock that is not reset automatically while using this trait, then it should be
155158
created via the companion object of `org.mockito.MockitoSugar` so is not tracked by this mechanism
156159

160+
## Idiomatic Mockito
161+
162+
By adding the trait `org.mockito.IdiomaticMockito` you get access to some improved methods in the API
163+
164+
This API is heavily inspired on Scalatest's Matchers, so if have used them, you'll find it very familiar
165+
166+
Here we can see the old syntax on the left and the new one on the right
167+
168+
```scala
169+
trait Foo {
170+
def bar: String
171+
def bar(v: Int): Int
172+
}
173+
174+
val aMock = mock[Foo]
175+
176+
when(aMock.bar) thenReturn "mocked!" <=> aMock.bar shouldReturn "mocked!"
177+
when(aMock.bar) thenReturn "mocked!" thenReturn "mocked again!" <=> aMock.bar shouldReturn "mocked!" andThen "mocked again!"
178+
when(aMock.bar) thenCallRealMethod() <=> aMock.bar shouldCallRealMethod
179+
when(aMock.bar).thenThrow[IllegalArgumentException] <=> aMock.bar.shouldThrow[IllegalArgumentException]
180+
when(aMock.bar) thenThrow new IllegalArgumentException <=> aMock.bar shouldThrow new IllegalArgumentException
181+
when(aMock.bar) thenAnswer(_ => "mocked!") <=> aMock.bar shouldAnswer "mocked!"
182+
when(aMock.bar(any)) thenAnswer(_.getArgument[Int](0) * 10) <=> aMock.bar(*) shouldAnswer ((i: Int) => i * 10)
183+
184+
verifyZeroInteractions(aMock) <=> aMock was never called
185+
verify(aMock).bar <=> aMock wasCalled on bar
186+
verify(aMock, only).bar <=> aMock wasCalled onlyOn bar
187+
verify(aMock, never).bar <=> aMock was never called on bar
188+
verify(aMock, times(2)).bar <=> aMock wasCalled twiceOn bar
189+
verify(aMock, times(6)).bar <=> aMock wasCalled sixTimesOn bar
190+
verifyNoMoreInteractions(aMock) <=> aMock was never called again
191+
```
192+
193+
As you can see the new syntax reads a bit more natural, also notice you can use `*` instead of `any[T]`
194+
195+
Check the [tests](https://github.com/mockito/mockito-scala/blob/master/core/src/test/scala/org/mockito/IdiomaticSyntaxTest.scala) for more examples
196+
157197
## Experimental features
158198

159199
* **by-name** arguments is currently an experimental feature as the implementation is a bit hacky and it gave some people problems
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package org.mockito
2+
3+
import org.mockito.stubbing.{Answer, OngoingStubbing}
4+
import MockitoSugar.{verify, _}
5+
6+
import scala.reflect.ClassTag
7+
import scala.reflect.runtime.universe.TypeTag
8+
9+
trait IdiomaticMockito {
10+
11+
def mock[T <: AnyRef: ClassTag: TypeTag](name: String): T = MockitoSugar.mock(name)
12+
13+
def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T = MockitoSugar.mock(mockSettings)
14+
15+
def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T = MockitoSugar.mock(defaultAnswer)
16+
17+
def mock[T <: AnyRef: ClassTag: TypeTag]: T = MockitoSugar.mock
18+
19+
implicit class StubbingOps[T](stubbing: => T) {
20+
21+
def shouldReturn(value: T): OngoingStubbing[T] = when(stubbing) thenReturn value
22+
23+
def shouldCallRealMethod: OngoingStubbing[T] = when(stubbing) thenCallRealMethod ()
24+
25+
def shouldThrow[E <: Throwable: ClassTag]: OngoingStubbing[T] = when(stubbing) thenThrow clazz
26+
27+
def shouldThrow[E <: Throwable](e: E): OngoingStubbing[T] = when(stubbing) thenThrow e
28+
29+
def shouldAnswer(f: => T): OngoingStubbing[T] = when(stubbing) thenAnswer (_ => f)
30+
31+
def shouldAnswer[P0](f: P0 => T): OngoingStubbing[T] = when(stubbing) thenAnswer (i => f(i.getArgument[P0](0)))
32+
33+
def shouldAnswer[P0, P1](f: (P0, P1) => T): OngoingStubbing[T] =
34+
when(stubbing) thenAnswer (i => f(i.getArgument[P0](0), i.getArgument[P1](1)))
35+
36+
def shouldAnswer[P0, P1, P2](f: (P0, P1, P2) => T): OngoingStubbing[T] =
37+
when(stubbing) thenAnswer (i => f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2)))
38+
39+
def shouldAnswer[P0, P1, P2, P3](f: (P0, P1, P2, P3) => T): OngoingStubbing[T] =
40+
when(stubbing) thenAnswer (i =>
41+
f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2), i.getArgument[P3](3)))
42+
43+
def shouldAnswer[P0, P1, P2, P3, P4](f: (P0, P1, P2, P3, P4) => T): OngoingStubbing[T] =
44+
when(stubbing) thenAnswer (i =>
45+
f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2), i.getArgument[P3](3), i.getArgument[P4](4)))
46+
47+
def shouldAnswer[P0, P1, P2, P3, P4, P5](f: (P0, P1, P2, P3, P4, P5) => T): OngoingStubbing[T] =
48+
when(stubbing) thenAnswer (i =>
49+
f(i.getArgument[P0](0),
50+
i.getArgument[P1](1),
51+
i.getArgument[P2](2),
52+
i.getArgument[P3](3),
53+
i.getArgument[P4](4),
54+
i.getArgument[P5](5)))
55+
56+
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6](f: (P0, P1, P2, P3, P4, P5, P6) => T): OngoingStubbing[T] =
57+
when(stubbing) thenAnswer (i =>
58+
f(i.getArgument[P0](0),
59+
i.getArgument[P1](1),
60+
i.getArgument[P2](2),
61+
i.getArgument[P3](3),
62+
i.getArgument[P4](4),
63+
i.getArgument[P5](5),
64+
i.getArgument[P6](6)))
65+
66+
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7](f: (P0, P1, P2, P3, P4, P5, P6, P7) => T): OngoingStubbing[T] =
67+
when(stubbing) thenAnswer (i =>
68+
f(
69+
i.getArgument[P0](0),
70+
i.getArgument[P1](1),
71+
i.getArgument[P2](2),
72+
i.getArgument[P3](3),
73+
i.getArgument[P4](4),
74+
i.getArgument[P5](5),
75+
i.getArgument[P6](6),
76+
i.getArgument[P7](7)
77+
))
78+
79+
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7, P8](
80+
f: (P0, P1, P2, P3, P4, P5, P6, P7, P8) => T): OngoingStubbing[T] =
81+
when(stubbing) thenAnswer (i =>
82+
f(
83+
i.getArgument[P0](0),
84+
i.getArgument[P1](1),
85+
i.getArgument[P2](2),
86+
i.getArgument[P3](3),
87+
i.getArgument[P4](4),
88+
i.getArgument[P5](5),
89+
i.getArgument[P6](6),
90+
i.getArgument[P7](7),
91+
i.getArgument[P8](8)
92+
))
93+
94+
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7, P8, P9](
95+
f: (P0, P1, P2, P3, P4, P5, P6, P7, P8, P9) => T): OngoingStubbing[T] =
96+
when(stubbing) thenAnswer (i =>
97+
f(
98+
i.getArgument[P0](0),
99+
i.getArgument[P1](1),
100+
i.getArgument[P2](2),
101+
i.getArgument[P3](3),
102+
i.getArgument[P4](4),
103+
i.getArgument[P5](5),
104+
i.getArgument[P6](6),
105+
i.getArgument[P7](7),
106+
i.getArgument[P8](8),
107+
i.getArgument[P9](9)
108+
))
109+
110+
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10](
111+
f: (P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) => T): OngoingStubbing[T] =
112+
when(stubbing) thenAnswer (i =>
113+
f(
114+
i.getArgument[P0](0),
115+
i.getArgument[P1](1),
116+
i.getArgument[P2](2),
117+
i.getArgument[P3](3),
118+
i.getArgument[P4](4),
119+
i.getArgument[P5](5),
120+
i.getArgument[P6](6),
121+
i.getArgument[P7](7),
122+
i.getArgument[P8](8),
123+
i.getArgument[P9](9),
124+
i.getArgument[P10](10)
125+
))
126+
127+
}
128+
129+
implicit class OngoingStubbingOps[T](ongoingStubbing: OngoingStubbing[T]) {
130+
def andThen(value: T): OngoingStubbing[T] = ongoingStubbing thenReturn value
131+
}
132+
133+
class On
134+
class OnlyOn
135+
class Never
136+
//noinspection UnitMethodIsParameterless
137+
case class NeverInstance[T <: AnyRef](mock: T) {
138+
def called: Unit = verifyZeroInteractions(mock)
139+
def called(on: On): T = verify(mock, MockitoSugar.never)
140+
def called(again: Again): Unit = verifyNoMoreInteractions(mock)
141+
}
142+
class Again
143+
case class Times(times: Int)
144+
145+
val on = new On
146+
val onlyOn = new OnlyOn
147+
val never = new Never
148+
val again = new Again
149+
val onceOn = Times(1)
150+
val twiceOn = Times(2)
151+
val thriceOn = Times(3)
152+
val threeTimesOn = Times(3)
153+
val fourTimesOn = Times(4)
154+
val fiveTimesOn = Times(5)
155+
val sixTimesOn = Times(6)
156+
val sevenTimesOn = Times(7)
157+
val eightTimesOn = Times(8)
158+
val nineTimesOn = Times(9)
159+
val tenTimesOn = Times(10)
160+
161+
implicit class VerificationOps[T <: AnyRef](mock: T) {
162+
163+
def wasCalled(on: On): T = verify(mock)
164+
165+
def wasCalled(t: Times): T = verify(mock, times(t.times))
166+
167+
def wasCalled(onlyOn: OnlyOn): T = verify(mock, only)
168+
169+
def was(n: Never): NeverInstance[T] = NeverInstance(mock)
170+
171+
}
172+
173+
def *[T]: T = ArgumentMatchersSugar.any[T]
174+
}

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
package org.mockito
1313

1414
import org.mockito.MockitoEnhancerUtil.stubMock
15-
import org.mockito.MockitoSugar.clazz
1615
import org.mockito.stubbing.{ Answer, OngoingStubbing, Stubber }
1716
import org.mockito.verification.{ VerificationMode, VerificationWithTimeout }
1817

@@ -307,7 +306,4 @@ trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications {
307306
/**
308307
* Simple object to allow the usage of the trait without mixing it in
309308
*/
310-
object MockitoSugar extends MockitoSugar {
311-
private[mockito] def clazz[T <: AnyRef](implicit classTag: ClassTag[T]) =
312-
classTag.runtimeClass.asInstanceOf[Class[T]]
313-
}
309+
object MockitoSugar extends MockitoSugar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org
2+
import scala.reflect.ClassTag
3+
4+
package object mockito {
5+
def clazz[T <: AnyRef](implicit classTag: ClassTag[T]): Class[T] = classTag.runtimeClass.asInstanceOf[Class[T]]
6+
}

0 commit comments

Comments
 (0)