Skip to content

Support Scala 2.11 #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The most popular mocking framework for Java, now in Scala!!!
[![Build Status](https://travis-ci.org/mockito/mockito-scala.svg?branch=master)](https://travis-ci.org/mockito/mockito-scala)

[![Download](https://api.bintray.com/packages/mockito/maven/mockito-scala/images/download.svg) ](https://bintray.com/mockito/maven/mockito-scala/_latestVersion)
[![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)
[![Maven Central](https://img.shields.io/maven-central/v/org.mockito/mockito-scala_2.12.svg)](https://search.maven.org/search?q=mockito-scala)
## Why separate project?

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

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


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

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!

## Notes for Scala 2.11

Please note that in Scala 2.11 the following features are not supported

* 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)
* Any kind of ArgumentMatchers for methods with by-name parameters (they'll throw an exception if used with ArgumentMatchers)

## Authors

* **Bruno Bonanno** - *Initial work* - [bbonanno](https://github.com/bbonanno)
Expand Down
13 changes: 6 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import scala.io.Source
import scala.language.postfixOps
import scala.util.Try

val _scalaVersion = "2.12.6"

lazy val commonSettings =
Seq(
scalaVersion := _scalaVersion,
organization := "org.mockito",
//Load version from the file so that Gradle/Shipkit and SBT use the same version
version := {
Expand All @@ -19,13 +16,13 @@ lazy val commonSettings =
}.get)
source.close
version.get
}
},
crossScalaVersions := Seq("2.11.12", "2.12.6")
)

lazy val commonLibraries = Seq(
"org.mockito" % "mockito-core" % "2.21.0",
"org.scala-lang" % "scala-reflect" % _scalaVersion,
"org.scalatest" %% "scalatest" % "3.0.5" % "provided"
"org.mockito" % "mockito-core" % "2.21.0",
"org.scalatest" %% "scalatest" % "3.0.5" % "provided"
)

lazy val core = (project in file("core"))
Expand All @@ -34,6 +31,7 @@ lazy val core = (project in file("core"))
commonSettings,
name := "mockito-scala",
libraryDependencies ++= commonLibraries,
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
// include the macro classes and resources in the main jar
mappings in (Compile, packageBin) ++= mappings
.in(macroSub, Compile, packageBin)
Expand Down Expand Up @@ -64,6 +62,7 @@ lazy val macroSub = (project in file("macro"))
.settings(
commonSettings,
libraryDependencies ++= commonLibraries,
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
publish := {},
publishLocal := {},
publishArtifact := false
Expand Down
23 changes: 21 additions & 2 deletions core/src/main/java/org/mockito/MockitoEnhancerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;

import static java.lang.reflect.Modifier.isStatic;
import static java.text.MessageFormat.format;
import static java.util.stream.Collectors.toSet;
import static org.mockito.Mockito.when;

/**
Expand All @@ -30,8 +32,25 @@
*/
class MockitoEnhancerUtil {

static <T> T stubMock(T aMock) {
Stream.of(aMock.getClass().getMethods())
static <T> T stubConcreteDefaultMethods(T aMock) {

Set<String> interfaceMethods = Stream.of(aMock.getClass().getInterfaces())
.flatMap(i -> Stream.of(i.getMethods()))
.map(Method::getName)
.collect(toSet());

return stubDefaultMethods(
aMock,
Stream.of(aMock.getClass().getMethods()).filter(m -> !interfaceMethods.contains(m.getName()))
);
}

static <T> T stubAllDefaultMethods(T aMock) {
return stubDefaultMethods(aMock, Stream.of(aMock.getClass().getMethods()));
}

private static <T> T stubDefaultMethods(T aMock, Stream<Method> methods) {
methods
.filter(m -> !isStatic(m.getModifiers()))
.filter(m -> m.getName().contains("$default$"))
.forEach(defaultParamMethod -> when(call(defaultParamMethod, aMock)).thenCallRealMethod());
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/scala-2.11/org/mockito/MockitoSugar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.mockito

trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications with Rest {
override def stubMock[T](mock: T): T = MockitoEnhancerUtil.stubConcreteDefaultMethods(mock)
}

/**
* Simple object to allow the usage of the trait without mixing it in
*/
object MockitoSugar extends MockitoSugar
10 changes: 10 additions & 0 deletions core/src/main/scala-2.12/org/mockito/MockitoSugar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.mockito

trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications with Rest {
override def stubMock[T](mock: T): T = MockitoEnhancerUtil.stubAllDefaultMethods(mock)
}

/**
* Simple object to allow the usage of the trait without mixing it in
*/
object MockitoSugar extends MockitoSugar
34 changes: 17 additions & 17 deletions core/src/main/scala/org/mockito/IdiomaticMockito.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import MockitoSugar.{verify, _}
import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeTag

trait IdiomaticMockito {
trait IdiomaticMockito extends MockCreator {

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

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

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

def mock[T <: AnyRef: ClassTag: TypeTag]: T = MockitoSugar.mock
override def mock[T <: AnyRef: ClassTag: TypeTag]: T = MockitoSugar.mock[T]

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

Expand All @@ -26,26 +26,26 @@ trait IdiomaticMockito {

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

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

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

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

def shouldAnswer[P0, P1, P2](f: (P0, P1, P2) => T): OngoingStubbing[T] =
when(stubbing) thenAnswer (i => f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2)))
when(stubbing) thenAnswer functionToAnswer(i => f(i.getArgument[P0](0), i.getArgument[P1](1), i.getArgument[P2](2)))

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

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

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

def shouldAnswer[P0, P1, P2, P3, P4, P5, P6](f: (P0, P1, P2, P3, P4, P5, P6) => T): OngoingStubbing[T] =
when(stubbing) thenAnswer (i =>
when(stubbing) thenAnswer functionToAnswer(i =>
f(i.getArgument[P0](0),
i.getArgument[P1](1),
i.getArgument[P2](2),
Expand All @@ -64,7 +64,7 @@ trait IdiomaticMockito {
i.getArgument[P6](6)))

def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7](f: (P0, P1, P2, P3, P4, P5, P6, P7) => T): OngoingStubbing[T] =
when(stubbing) thenAnswer (i =>
when(stubbing) thenAnswer functionToAnswer(i =>
f(
i.getArgument[P0](0),
i.getArgument[P1](1),
Expand All @@ -78,7 +78,7 @@ trait IdiomaticMockito {

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

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

def shouldAnswer[P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10](
f: (P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) => T): OngoingStubbing[T] =
when(stubbing) thenAnswer (i =>
when(stubbing) thenAnswer functionToAnswer(i =>
f(
i.getArgument[P0](0),
i.getArgument[P1](1),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

package org.mockito

import org.mockito.MockitoEnhancerUtil.stubMock
import org.mockito.stubbing.{ Answer, OngoingStubbing, Stubber }
import org.mockito.verification.{ VerificationMode, VerificationWithTimeout }
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.{Answer, OngoingStubbing, Stubber}
import org.mockito.verification.{VerificationMode, VerificationWithTimeout}

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

private[mockito] trait MockitoEnhancer extends MockCreator {
Expand Down Expand Up @@ -141,6 +141,8 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
stubMock(Mockito.mock(clazz, settings))
}

def stubMock[T](mock: T): T

/**
* Delegates to <code>Mockito.mock(type: Class[T], name: String)</code>
* It provides a nicer API as you can, for instance, do <code>mock[MyClass](name)</code>
Expand Down Expand Up @@ -245,7 +247,7 @@ private[mockito] trait Verifications {
*
* @author Bruno Bonanno
*/
trait MockitoSugar extends MockitoEnhancer with DoSomething with Verifications {
private[mockito] trait Rest extends MockitoEnhancer with DoSomething with Verifications {

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

}

/**
* Simple object to allow the usage of the trait without mixing it in
*/
object MockitoSugar extends MockitoSugar
6 changes: 5 additions & 1 deletion core/src/main/scala/org/mockito/MockitoScalaSession.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class MockitoScalaSession(name: String, strictness: Strictness, logger: MockitoS
object MockitoScalaSession {
def apply(name: String = "<Unnamed Session>",
strictness: Strictness = STRICT_STUBS,
logger: MockitoSessionLogger = println) = new MockitoScalaSession(name, strictness, logger)
logger: MockitoSessionLogger = MockitoScalaLogger) = new MockitoScalaSession(name, strictness, logger)

object SyntheticLocation extends Location
object SyntheticMethodInvocation extends DescribedInvocation {
Expand All @@ -51,4 +51,8 @@ object MockitoScalaSession {
case _ => ()
}
}
}

object MockitoScalaLogger extends MockitoSessionLogger {
override def log(hint: String): Unit = println(hint)
}
26 changes: 15 additions & 11 deletions core/src/main/scala/org/mockito/ReflectionUtils.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.mockito

import java.util.function

import org.mockito.internal.invocation.ArgumentsProcessor

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

def markMethodsWithLazyArgs(clazz: Class[_]): Unit = {
ArgumentsProcessor.extractors.computeIfAbsent(clazz, { _ =>
val mirror = runtimeMirror(clazz.getClassLoader)
ArgumentsProcessor.extractors.computeIfAbsent(clazz, new function.Function[Class[_], ArgumentExtractor] {
override def apply(t: Class[_]): ArgumentExtractor = {
val mirror = runtimeMirror(clazz.getClassLoader)

val symbol = mirror.classSymbol(clazz)
val symbol = mirror.classSymbol(clazz)

val methodsWithLazyArgs = symbol.info.decls.collect {
case s if s.isMethod => (s.name.toString, s.typeSignature.paramLists.flatten.zipWithIndex.collect {
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
}.toSet)
}
.toMap
.filter(_._2.nonEmpty)
val methodsWithLazyArgs = symbol.info.decls.collect {
case s if s.isMethod => (s.name.toString, s.typeSignature.paramLists.flatten.zipWithIndex.collect {
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
}.toSet)
}
.toMap
.filter(_._2.nonEmpty)

ArgumentExtractor(methodsWithLazyArgs)
ArgumentExtractor(methodsWithLazyArgs)
}
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ package org.mockito.integrations.scalatest
import java.util.concurrent.ConcurrentHashMap

import org.mockito.stubbing.Answer
import org.mockito.{ MockCreator, MockSettings, MockitoSugar }
import org.scalatest.{ Outcome, TestSuite }
import org.mockito.{MockCreator, MockSettings, MockitoSugar}
import org.scalatest.{Outcome, TestSuite}

import scala.collection.JavaConverters._
import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeTag

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

private val mocksToReset = ConcurrentHashMap.newKeySet[AnyRef]()
private val mocksToReset = ConcurrentHashMap.newKeySet[AnyRef]().asScala

private def resetAll(): Unit = mocksToReset.forEach(MockitoSugar.reset(_))
private def resetAll(): Unit = mocksToReset.foreach(MockitoSugar.reset(_))

override protected def withFixture(test: NoArgTest): Outcome = {
val outcome = super.withFixture(test)
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/scala/org/mockito/package.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package org

import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer

import scala.reflect.ClassTag

package object mockito {
def clazz[T <: AnyRef](implicit classTag: ClassTag[T]): Class[T] = classTag.runtimeClass.asInstanceOf[Class[T]]

def functionToAnswer[T](f: InvocationOnMock => T): Answer[T] = new Answer[T] {
override def answer(invocation: InvocationOnMock): T = f(invocation)
}
}
Loading