Skip to content

Commit 73ad48d

Browse files
authored
Merge pull request #26 from mockito/support_2.11
Support Scala 2.11
2 parents 2ca3e77 + 1bcb420 commit 73ad48d

File tree

16 files changed

+220
-122
lines changed

16 files changed

+220
-122
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The most popular mocking framework for Java, now in Scala!!!
1111
[![Build Status](https://travis-ci.org/mockito/mockito-scala.svg?branch=master)](https://travis-ci.org/mockito/mockito-scala)
1212

1313
[![Download](https://api.bintray.com/packages/mockito/maven/mockito-scala/images/download.svg) ](https://bintray.com/mockito/maven/mockito-scala/_latestVersion)
14-
[![Maven Central](https://img.shields.io/maven-central/v/org.mockito/mockito-scala_2.12.svg)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.mockito%22%20AND%20a%3A%22mockito-scala_2.12%22)
14+
[![Maven Central](https://img.shields.io/maven-central/v/org.mockito/mockito-scala_2.12.svg)](https://search.maven.org/search?q=mockito-scala)
1515
## Why separate project?
1616

1717
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.
@@ -20,7 +20,7 @@ The library has independent developers, release cycle and versioning from core m
2020

2121
* Artifact identifier: "org.mockito:mockito-scala_2.12:VERSION"
2222
* Latest version - see [release notes](/docs/release-notes.md)
23-
* 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)
23+
* Repositories: [Maven Central](https://search.maven.org/search?q=mockito-scala) or [JFrog's Bintray](https://bintray.com/mockito/maven/mockito-scala)
2424

2525

2626
## Getting started
@@ -214,6 +214,13 @@ on which build tool and IDE you use, so try it out.
214214

215215
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!
216216

217+
## Notes for Scala 2.11
218+
219+
Please note that in Scala 2.11 the following features are not supported
220+
221+
* Default arguments on methods defined in traits (they will behave as before, getting `null` or a default value if they are of a primitive type)
222+
* Any kind of ArgumentMatchers for methods with by-name parameters (they'll throw an exception if used with ArgumentMatchers)
223+
217224
## Authors
218225

219226
* **Bruno Bonanno** - *Initial work* - [bbonanno](https://github.com/bbonanno)

build.sbt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ import scala.io.Source
44
import scala.language.postfixOps
55
import scala.util.Try
66

7-
val _scalaVersion = "2.12.6"
8-
97
lazy val commonSettings =
108
Seq(
11-
scalaVersion := _scalaVersion,
129
organization := "org.mockito",
1310
//Load version from the file so that Gradle/Shipkit and SBT use the same version
1411
version := {
@@ -19,13 +16,13 @@ lazy val commonSettings =
1916
}.get)
2017
source.close
2118
version.get
22-
}
19+
},
20+
crossScalaVersions := Seq("2.11.12", "2.12.6")
2321
)
2422

2523
lazy val commonLibraries = Seq(
26-
"org.mockito" % "mockito-core" % "2.21.0",
27-
"org.scala-lang" % "scala-reflect" % _scalaVersion,
28-
"org.scalatest" %% "scalatest" % "3.0.5" % "provided"
24+
"org.mockito" % "mockito-core" % "2.21.0",
25+
"org.scalatest" %% "scalatest" % "3.0.5" % "provided"
2926
)
3027

3128
lazy val core = (project in file("core"))
@@ -34,6 +31,7 @@ lazy val core = (project in file("core"))
3431
commonSettings,
3532
name := "mockito-scala",
3633
libraryDependencies ++= commonLibraries,
34+
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
3735
// include the macro classes and resources in the main jar
3836
mappings in (Compile, packageBin) ++= mappings
3937
.in(macroSub, Compile, packageBin)
@@ -64,6 +62,7 @@ lazy val macroSub = (project in file("macro"))
6462
.settings(
6563
commonSettings,
6664
libraryDependencies ++= commonLibraries,
65+
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
6766
publish := {},
6867
publishLocal := {},
6968
publishArtifact := false

core/src/main/java/org/mockito/MockitoEnhancerUtil.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313

1414
import java.lang.reflect.Method;
1515
import java.util.Arrays;
16+
import java.util.Set;
1617
import java.util.stream.Stream;
1718

1819
import static java.lang.reflect.Modifier.isStatic;
1920
import static java.text.MessageFormat.format;
21+
import static java.util.stream.Collectors.toSet;
2022
import static org.mockito.Mockito.when;
2123

2224
/**
@@ -30,8 +32,25 @@
3032
*/
3133
class MockitoEnhancerUtil {
3234

33-
static <T> T stubMock(T aMock) {
34-
Stream.of(aMock.getClass().getMethods())
35+
static <T> T stubConcreteDefaultMethods(T aMock) {
36+
37+
Set<String> interfaceMethods = Stream.of(aMock.getClass().getInterfaces())
38+
.flatMap(i -> Stream.of(i.getMethods()))
39+
.map(Method::getName)
40+
.collect(toSet());
41+
42+
return stubDefaultMethods(
43+
aMock,
44+
Stream.of(aMock.getClass().getMethods()).filter(m -> !interfaceMethods.contains(m.getName()))
45+
);
46+
}
47+
48+
static <T> T stubAllDefaultMethods(T aMock) {
49+
return stubDefaultMethods(aMock, Stream.of(aMock.getClass().getMethods()));
50+
}
51+
52+
private static <T> T stubDefaultMethods(T aMock, Stream<Method> methods) {
53+
methods
3554
.filter(m -> !isStatic(m.getModifiers()))
3655
.filter(m -> m.getName().contains("$default$"))
3756
.forEach(defaultParamMethod -> when(call(defaultParamMethod, aMock)).thenCallRealMethod());
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.mockito
2+
3+
trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications with Rest {
4+
override def stubMock[T](mock: T): T = MockitoEnhancerUtil.stubConcreteDefaultMethods(mock)
5+
}
6+
7+
/**
8+
* Simple object to allow the usage of the trait without mixing it in
9+
*/
10+
object MockitoSugar extends MockitoSugar
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.mockito
2+
3+
trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications with Rest {
4+
override def stubMock[T](mock: T): T = MockitoEnhancerUtil.stubAllDefaultMethods(mock)
5+
}
6+
7+
/**
8+
* Simple object to allow the usage of the trait without mixing it in
9+
*/
10+
object MockitoSugar extends MockitoSugar

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

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import MockitoSugar.{verify, _}
66
import scala.reflect.ClassTag
77
import scala.reflect.runtime.universe.TypeTag
88

9-
trait IdiomaticMockito {
9+
trait IdiomaticMockito extends MockCreator {
1010

11-
def mock[T <: AnyRef: ClassTag: TypeTag](name: String): T = MockitoSugar.mock(name)
11+
override def mock[T <: AnyRef: ClassTag: TypeTag](name: String): T = MockitoSugar.mock[T](name)
1212

13-
def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T = MockitoSugar.mock(mockSettings)
13+
override def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T = MockitoSugar.mock[T](mockSettings)
1414

15-
def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T = MockitoSugar.mock(defaultAnswer)
15+
override def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T = MockitoSugar.mock[T](defaultAnswer)
1616

17-
def mock[T <: AnyRef: ClassTag: TypeTag]: T = MockitoSugar.mock
17+
override def mock[T <: AnyRef: ClassTag: TypeTag]: T = MockitoSugar.mock[T]
1818

1919
implicit class StubbingOps[T](stubbing: => T) {
2020

@@ -26,26 +26,26 @@ trait IdiomaticMockito {
2626

2727
def shouldThrow[E <: Throwable](e: E): OngoingStubbing[T] = when(stubbing) thenThrow e
2828

29-
def shouldAnswer(f: => T): OngoingStubbing[T] = when(stubbing) thenAnswer (_ => f)
29+
def shouldAnswer(f: => T): OngoingStubbing[T] = when(stubbing) thenAnswer functionToAnswer(_ => f)
3030

31-
def shouldAnswer[P0](f: P0 => T): OngoingStubbing[T] = when(stubbing) thenAnswer (i => f(i.getArgument[P0](0)))
31+
def shouldAnswer[P0](f: P0 => T): OngoingStubbing[T] = when(stubbing) thenAnswer functionToAnswer(i => f(i.getArgument[P0](0)))
3232

3333
def shouldAnswer[P0, P1](f: (P0, P1) => T): OngoingStubbing[T] =
34-
when(stubbing) thenAnswer (i => f(i.getArgument[P0](0), i.getArgument[P1](1)))
34+
when(stubbing) thenAnswer functionToAnswer(i => f(i.getArgument[P0](0), i.getArgument[P1](1)))
3535

3636
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)))
37+
when(stubbing) thenAnswer functionToAnswer(i => f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2)))
3838

3939
def shouldAnswer[P0, P1, P2, P3](f: (P0, P1, P2, P3) => T): OngoingStubbing[T] =
40-
when(stubbing) thenAnswer (i =>
40+
when(stubbing) thenAnswer functionToAnswer(i =>
4141
f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2), i.getArgument[P3](3)))
4242

4343
def shouldAnswer[P0, P1, P2, P3, P4](f: (P0, P1, P2, P3, P4) => T): OngoingStubbing[T] =
44-
when(stubbing) thenAnswer (i =>
44+
when(stubbing) thenAnswer functionToAnswer(i =>
4545
f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2), i.getArgument[P3](3), i.getArgument[P4](4)))
4646

4747
def shouldAnswer[P0, P1, P2, P3, P4, P5](f: (P0, P1, P2, P3, P4, P5) => T): OngoingStubbing[T] =
48-
when(stubbing) thenAnswer (i =>
48+
when(stubbing) thenAnswer functionToAnswer(i =>
4949
f(i.getArgument[P0](0),
5050
i.getArgument[P1](1),
5151
i.getArgument[P2](2),
@@ -54,7 +54,7 @@ trait IdiomaticMockito {
5454
i.getArgument[P5](5)))
5555

5656
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 =>
57+
when(stubbing) thenAnswer functionToAnswer(i =>
5858
f(i.getArgument[P0](0),
5959
i.getArgument[P1](1),
6060
i.getArgument[P2](2),
@@ -64,7 +64,7 @@ trait IdiomaticMockito {
6464
i.getArgument[P6](6)))
6565

6666
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 =>
67+
when(stubbing) thenAnswer functionToAnswer(i =>
6868
f(
6969
i.getArgument[P0](0),
7070
i.getArgument[P1](1),
@@ -78,7 +78,7 @@ trait IdiomaticMockito {
7878

7979
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7, P8](
8080
f: (P0, P1, P2, P3, P4, P5, P6, P7, P8) => T): OngoingStubbing[T] =
81-
when(stubbing) thenAnswer (i =>
81+
when(stubbing) thenAnswer functionToAnswer(i =>
8282
f(
8383
i.getArgument[P0](0),
8484
i.getArgument[P1](1),
@@ -93,7 +93,7 @@ trait IdiomaticMockito {
9393

9494
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7, P8, P9](
9595
f: (P0, P1, P2, P3, P4, P5, P6, P7, P8, P9) => T): OngoingStubbing[T] =
96-
when(stubbing) thenAnswer (i =>
96+
when(stubbing) thenAnswer functionToAnswer(i =>
9797
f(
9898
i.getArgument[P0](0),
9999
i.getArgument[P1](1),
@@ -109,7 +109,7 @@ trait IdiomaticMockito {
109109

110110
def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10](
111111
f: (P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) => T): OngoingStubbing[T] =
112-
when(stubbing) thenAnswer (i =>
112+
when(stubbing) thenAnswer functionToAnswer(i =>
113113
f(
114114
i.getArgument[P0](0),
115115
i.getArgument[P1](1),

core/src/main/scala/org/mockito/MockitoSugar.scala renamed to core/src/main/scala/org/mockito/MockitoAPI.scala

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

1212
package org.mockito
1313

14-
import org.mockito.MockitoEnhancerUtil.stubMock
15-
import org.mockito.stubbing.{ Answer, OngoingStubbing, Stubber }
16-
import org.mockito.verification.{ VerificationMode, VerificationWithTimeout }
14+
import org.mockito.invocation.InvocationOnMock
15+
import org.mockito.stubbing.{Answer, OngoingStubbing, Stubber}
16+
import org.mockito.verification.{VerificationMode, VerificationWithTimeout}
1717

1818
import scala.collection.JavaConverters._
1919
import scala.reflect.ClassTag
@@ -79,7 +79,7 @@ private[mockito] trait DoSomething {
7979
/**
8080
* Delegates to <code>Mockito.doAnswer()</code>, it's only here to expose the full Mockito API
8181
*/
82-
def doAnswer(answer: Answer[_]): Stubber = Mockito.doAnswer(answer)
82+
def doAnswer[T](f: InvocationOnMock => T): Stubber = Mockito.doAnswer(functionToAnswer(f))
8383
}
8484

8585
private[mockito] trait MockitoEnhancer extends MockCreator {
@@ -141,6 +141,8 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
141141
stubMock(Mockito.mock(clazz, settings))
142142
}
143143

144+
def stubMock[T](mock: T): T
145+
144146
/**
145147
* Delegates to <code>Mockito.mock(type: Class[T], name: String)</code>
146148
* It provides a nicer API as you can, for instance, do <code>mock[MyClass](name)</code>
@@ -245,7 +247,7 @@ private[mockito] trait Verifications {
245247
*
246248
* @author Bruno Bonanno
247249
*/
248-
trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications {
250+
private[mockito] trait Rest extends MockitoEnhancer with DoSomething with Verifications {
249251

250252
/**
251253
* Delegates to <code>ArgumentCaptor.forClass(type: Class[T])</code>
@@ -302,8 +304,3 @@ trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications {
302304
def verify[T](mock: T, mode: VerificationMode): T = Mockito.verify(mock, mode)
303305

304306
}
305-
306-
/**
307-
* Simple object to allow the usage of the trait without mixing it in
308-
*/
309-
object MockitoSugar extends MockitoSugar

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class MockitoScalaSession(name: String, strictness: Strictness, logger: MockitoS
2727
object MockitoScalaSession {
2828
def apply(name: String = "<Unnamed Session>",
2929
strictness: Strictness = STRICT_STUBS,
30-
logger: MockitoSessionLogger = println) = new MockitoScalaSession(name, strictness, logger)
30+
logger: MockitoSessionLogger = MockitoScalaLogger) = new MockitoScalaSession(name, strictness, logger)
3131

3232
object SyntheticLocation extends Location
3333
object SyntheticMethodInvocation extends DescribedInvocation {
@@ -51,4 +51,8 @@ object MockitoScalaSession {
5151
case _ => ()
5252
}
5353
}
54+
}
55+
56+
object MockitoScalaLogger extends MockitoSessionLogger {
57+
override def log(hint: String): Unit = println(hint)
5458
}

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.mockito
22

3+
import java.util.function
4+
35
import org.mockito.internal.invocation.ArgumentsProcessor
46

57
import scala.reflect.runtime.universe._
@@ -16,20 +18,22 @@ private[mockito] object ReflectionUtils {
1618
}
1719

1820
def markMethodsWithLazyArgs(clazz: Class[_]): Unit = {
19-
ArgumentsProcessor.extractors.computeIfAbsent(clazz, { _ =>
20-
val mirror = runtimeMirror(clazz.getClassLoader)
21+
ArgumentsProcessor.extractors.computeIfAbsent(clazz, new function.Function[Class[_], ArgumentExtractor] {
22+
override def apply(t: Class[_]): ArgumentExtractor = {
23+
val mirror = runtimeMirror(clazz.getClassLoader)
2124

22-
val symbol = mirror.classSymbol(clazz)
25+
val symbol = mirror.classSymbol(clazz)
2326

24-
val methodsWithLazyArgs = symbol.info.decls.collect {
25-
case s if s.isMethod => (s.name.toString, s.typeSignature.paramLists.flatten.zipWithIndex.collect {
26-
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
27-
}.toSet)
28-
}
29-
.toMap
30-
.filter(_._2.nonEmpty)
27+
val methodsWithLazyArgs = symbol.info.decls.collect {
28+
case s if s.isMethod => (s.name.toString, s.typeSignature.paramLists.flatten.zipWithIndex.collect {
29+
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
30+
}.toSet)
31+
}
32+
.toMap
33+
.filter(_._2.nonEmpty)
3134

32-
ArgumentExtractor(methodsWithLazyArgs)
35+
ArgumentExtractor(methodsWithLazyArgs)
36+
}
3337
})
3438
}
3539
}

core/src/main/scala/org/mockito/integrations/scalatest/ResetMocksAfterEachTest.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ package org.mockito.integrations.scalatest
33
import java.util.concurrent.ConcurrentHashMap
44

55
import org.mockito.stubbing.Answer
6-
import org.mockito.{ MockCreator, MockSettings, MockitoSugar }
7-
import org.scalatest.{ Outcome, TestSuite }
6+
import org.mockito.{MockCreator, MockSettings, MockitoSugar}
7+
import org.scalatest.{Outcome, TestSuite}
88

9+
import scala.collection.JavaConverters._
910
import scala.reflect.ClassTag
1011
import scala.reflect.runtime.universe.TypeTag
1112

1213
trait ResetMocksAfterEachTest extends TestSuite with MockCreator { self: MockCreator =>
1314

14-
private val mocksToReset = ConcurrentHashMap.newKeySet[AnyRef]()
15+
private val mocksToReset = ConcurrentHashMap.newKeySet[AnyRef]().asScala
1516

16-
private def resetAll(): Unit = mocksToReset.forEach(MockitoSugar.reset(_))
17+
private def resetAll(): Unit = mocksToReset.foreach(MockitoSugar.reset(_))
1718

1819
override protected def withFixture(test: NoArgTest): Outcome = {
1920
val outcome = super.withFixture(test)

0 commit comments

Comments
 (0)