Skip to content

Commit e42ee36

Browse files
committed
Make ByName arguments an experimental feature
1 parent 14c18bb commit e42ee36

File tree

3 files changed

+56
-30
lines changed

3 files changed

+56
-30
lines changed

README.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@ 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-
**IMPORTANT!** mockito-scala will pull the right version of mockito-core as a transitive dependency,
27-
but if for some reason you'd like to have both libraries explicitly declared in your build file, please make sure
28-
you declare mockito-core first, as we currently rely on a class loader hack to make by-name arguments work.
29-
30-
3126
For a more detailed explanation of the features please read [this](https://medium.com/@bbonanno_83496/introduction-to-mockito-scala-ede30769cbda) article series
3227

3328

@@ -43,7 +38,7 @@ This trait wraps the API available on `org.mockito.Mockito` from the Java versio
4338
* Eliminates parenthesis when possible to make the test code more readable
4439
* Adds `spyLambda[T]` to allow spying lambdas (they don't work with the standard spy as they are created as final classes by the compiler)
4540
* Supports mocking inline mixins like `mock[MyClass with MyTrait]`
46-
* Supports by-name arguments in some scenarios
41+
* Supports by-name arguments in some scenarios [EXPERIMENTAL](#experimental-features)
4742
* Full support when all arguments in a method are by-name
4843
* Full support when only some arguments in a method are by-name, but we use the `any[T]` matcher for every argument
4944
* Full support when only some arguments in a method are by-name, but we use NO matchers at all
@@ -75,6 +70,25 @@ The matchers for the value classes always require the type to be explicit, apart
7570
verify(myObj).myMethod(eqToVal[MyValueClass](456))
7671
```
7772

73+
## Experimental features
74+
75+
* **by-name** arguments is currently an experimental feature as the implementation is a bit hacky and it gave some people problems
76+
77+
If you want to use it, you have to mix-in an extra trait (`org.mockito.ByNameExperimental`)
78+
in your test class, after `org.mockito.MockitoSugar`, so your test file would look like
79+
80+
```
81+
class MyTest extends WordSpec with MockitoSugar with ByNameExperimental
82+
```
83+
84+
It is important to notice that this feature relies on the class loader order to work, as we currently have to override a class
85+
from `mockito-core`, you should not have to do anything special to make it work, `mockito-scala` already pulls the right
86+
version of `mockito-core` as a transitive dependency and it should be the only dependency you need, **BUT**, if you start getting
87+
weird exceptions while trying to use `org.mockito.ByNameExperimental` you can try to be explicit with the `mockito-core` dependency.
88+
I can't tell you if you should put it before or after the `mockito-scala` dependency in your build file as it depends a lot
89+
on which build tool and IDE you use, so try it out.
90+
91+
We are working with the mockito-core developers to add the necessary features in it so we can get rid of this hack as soon as we can, stay tuned!
7892

7993
## Authors
8094

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

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ import scala.collection.JavaConverters._
2020
import scala.reflect.ClassTag
2121
import scala.reflect.runtime.universe.TypeTag
2222

23+
private[mockito] trait MockCreator {
24+
def mock[T <: AnyRef: ClassTag: TypeTag]: T
25+
def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T
26+
def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T
27+
def mock[T <: AnyRef: ClassTag: TypeTag](name: String): T
28+
}
29+
30+
trait ByNameExperimental extends MockCreator {
31+
32+
abstract override def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T = {
33+
ReflectionUtils.markMethodsWithLazyArgs(clazz)
34+
super.mock[T](mockSettings)
35+
}
36+
37+
}
38+
2339
private[mockito] trait DoSomething {
2440

2541
/**
@@ -67,7 +83,7 @@ private[mockito] trait DoSomething {
6783
def doAnswer(answer: Answer[_]): Stubber = Mockito.doAnswer(answer)
6884
}
6985

70-
private[mockito] trait MockitoEnhancer {
86+
private[mockito] trait MockitoEnhancer extends MockCreator {
7187

7288
/**
7389
* Delegates to <code>Mockito.mock(type: Class[T])</code>
@@ -83,7 +99,7 @@ private[mockito] trait MockitoEnhancer {
8399
* <code>verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value")</code>
84100
* as the value for the second parameter would have been null...
85101
*/
86-
def mock[T <: AnyRef: ClassTag: TypeTag]: T = mock(withSettings)
102+
override def mock[T <: AnyRef: ClassTag: TypeTag]: T = mock(withSettings)
87103

88104
/**
89105
* Delegates to <code>Mockito.mock(type: Class[T], defaultAnswer: Answer[_])</code>
@@ -99,7 +115,7 @@ private[mockito] trait MockitoEnhancer {
99115
* <code>verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value")</code>
100116
* as the value for the second parameter would have been null...
101117
*/
102-
def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T =
118+
override def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T =
103119
mock(withSettings.defaultAnswer(defaultAnswer))
104120

105121
/**
@@ -116,11 +132,9 @@ private[mockito] trait MockitoEnhancer {
116132
* <code>verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value")</code>
117133
* as the value for the second parameter would have been null...
118134
*/
119-
def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T = {
135+
override def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T = {
120136
val interfaces = ReflectionUtils.interfaces
121137

122-
ReflectionUtils.markMethodsWithLazyArgs(clazz)
123-
124138
val settings =
125139
if (interfaces.nonEmpty) mockSettings.extraInterfaces(interfaces: _*)
126140
else mockSettings
@@ -142,8 +156,7 @@ private[mockito] trait MockitoEnhancer {
142156
* <code>verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value")</code>
143157
* as the value for the second parameter would have been null...
144158
*/
145-
def mock[T <: AnyRef: ClassTag: TypeTag](name: String): T =
146-
mock(withSettings.name(name))
159+
override def mock[T <: AnyRef: ClassTag: TypeTag](name: String): T = mock(withSettings.name(name))
147160

148161
/**
149162
* Delegates to <code>Mockito.reset(T... mocks)</code>, but restores the default stubs that
@@ -157,8 +170,7 @@ private[mockito] trait MockitoEnhancer {
157170
/**
158171
* Delegates to <code>Mockito.mockingDetails()</code>, it's only here to expose the full Mockito API
159172
*/
160-
def mockingDetails(toInspect: AnyRef): MockingDetails =
161-
Mockito.mockingDetails(toInspect)
173+
def mockingDetails(toInspect: AnyRef): MockingDetails = Mockito.mockingDetails(toInspect)
162174

163175
/**
164176
* Delegates to <code>Mockito.withSettings()</code>, it's only here to expose the full Mockito API
@@ -206,26 +218,22 @@ private[mockito] trait Verifications {
206218
/**
207219
* Delegates to <code>Mockito.times()</code>, it's only here to expose the full Mockito API
208220
*/
209-
def times(wantedNumberOfInvocations: Int): VerificationMode =
210-
Mockito.times(wantedNumberOfInvocations)
221+
def times(wantedNumberOfInvocations: Int): VerificationMode = Mockito.times(wantedNumberOfInvocations)
211222

212223
/**
213224
* Delegates to <code>Mockito.calls()</code>, it's only here to expose the full Mockito API
214225
*/
215-
def calls(wantedNumberOfInvocations: Int): VerificationMode =
216-
Mockito.calls(wantedNumberOfInvocations)
226+
def calls(wantedNumberOfInvocations: Int): VerificationMode = Mockito.calls(wantedNumberOfInvocations)
217227

218228
/**
219229
* Delegates to <code>Mockito.atMost()</code>, it's only here to expose the full Mockito API
220230
*/
221-
def atMost(maxNumberOfInvocations: Int): VerificationMode =
222-
Mockito.atMost(maxNumberOfInvocations)
231+
def atMost(maxNumberOfInvocations: Int): VerificationMode = Mockito.atMost(maxNumberOfInvocations)
223232

224233
/**
225234
* Delegates to <code>Mockito.atLeast()</code>, it's only here to expose the full Mockito API
226235
*/
227-
def atLeast(minNumberOfInvocations: Int): VerificationMode =
228-
Mockito.atLeast(minNumberOfInvocations)
236+
def atLeast(minNumberOfInvocations: Int): VerificationMode = Mockito.atLeast(minNumberOfInvocations)
229237
}
230238

231239
/**
@@ -268,8 +276,7 @@ trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications {
268276
/**
269277
* Delegates to <code>Mockito.ignoreStubs()</code>, it's only here to expose the full Mockito API
270278
*/
271-
def ignoreStubs(mocks: AnyRef*): Array[AnyRef] =
272-
Mockito.ignoreStubs(mocks: _*)
279+
def ignoreStubs(mocks: AnyRef*): Array[AnyRef] = Mockito.ignoreStubs(mocks: _*)
273280

274281
/**
275282
* Delegates to <code>Mockito.validateMockitoUsage()</code>, it's only here to expose the full Mockito API
@@ -279,8 +286,7 @@ trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications {
279286
/**
280287
* Delegates to <code>Mockito.verifyZeroInteractions()</code>, it's only here to expose the full Mockito API
281288
*/
282-
def verifyZeroInteractions(mocks: AnyRef*): Unit =
283-
Mockito.verifyZeroInteractions(mocks: _*)
289+
def verifyZeroInteractions(mocks: AnyRef*): Unit = Mockito.verifyZeroInteractions(mocks: _*)
284290

285291
/**
286292
* Delegates to <code>Mockito.inOrder()</code>, it's only here to expose the full Mockito API

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ package org.mockito
33
import org.scalatest
44
import org.scalatest.WordSpec
55

6-
class MockitoSugarTest extends WordSpec with MockitoSugar with scalatest.Matchers with ArgumentMatchersSugar {
6+
class MockitoSugarTest
7+
extends WordSpec
8+
with MockitoSugar
9+
with scalatest.Matchers
10+
with ArgumentMatchersSugar
11+
with ByNameExperimental {
712

813
class Foo {
914
def bar = "not mocked"
@@ -17,7 +22,8 @@ class MockitoSugarTest extends WordSpec with MockitoSugar with scalatest.Matcher
1722

1823
def iHaveFunction0Args(normal: String, f0: () => String): String = s"$normal - $f0"
1924

20-
def iHaveByNameAndFunction0Args(normal: String, f0: () => String, byName: => String): String = s"$normal - $byName - $f0"
25+
def iHaveByNameAndFunction0Args(normal: String, f0: () => String, byName: => String): String =
26+
s"$normal - $byName - $f0"
2127
}
2228

2329
class Bar {

0 commit comments

Comments
 (0)