Skip to content

Upgrade mockito-core to 5.18.0, add support for JDK 17 and 21, drop Scala 2.11 and JDK 8 #559

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 2 commits into from
May 27, 2025
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
# Definition of the build matrix
strategy:
matrix:
java: [8, 11, 14]
java: [11, 17, 21]

# All build steps
# SINGLE-MATRIX-JOB means that the step does not need to be executed on every job in the matrix
Expand Down Expand Up @@ -65,11 +65,11 @@ jobs:
with:
fetch-depth: '0' # https://github.com/shipkit/shipkit-changelog#fetch-depth-on-ci

- name: Set up Java 8
- name: Set up Java 17
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 8
java-version: 17

- name: Build and publish to Bintray/MavenCentral
run: ./gradlew writeActualVersion
Expand Down
1 change: 0 additions & 1 deletion .sbtopts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
-J-Xmx8G
-J-XX:MaxMetaspaceSize=1G
-J-XX:+CMSClassUnloadingEnabled
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ The library has independent developers, release cycle and versioning from core m
- [Scalaz](/scalaz/src/test)

## Partial unification
If you're in Scala 2.11 or 2.12 you'll probably want to add the compiler flag `-Ypartial-unification`, if you don't you risk some compile errors when trying to stub complex types using the idiomatic syntax
If you're in Scala 2.12 you'll probably want to add the compiler flag `-Ypartial-unification`, if you don't you risk some compile errors when trying to stub complex types using the idiomatic syntax

## Notes for 2.0.0
We dropped support for Scala 2.11 and Java 8, as Mockito 5 dropped support for Java 8.

## Notes for 1.13.6

Expand Down Expand Up @@ -500,14 +503,14 @@ Of course you can override the default behaviour, for this you have 2 options
DefaultAnswers are also composable, so for example if you wanted empty values first and then smart nulls you could do `implicit val defaultAnswer: DefaultAnswer = ReturnsEmptyValues orElse ReturnsSmartNulls`

## Function Answers
`org.mockito.Answer[T]` can be a bit boilerplate-ish, mostly if you're still in Scala 2.11 (in 2.12 with SAM is much nicer),
`org.mockito.Answer[T]` can be a bit boilerplate-ish,
to simplify the usage for both versions is that we replaced it by standard scala functions, so instead of
```scala
when(myMock.foo("bar", 42)) thenAnswer new Answer[String] {
override def answer(invocation: InvocationOnMock): String = i.getArgument[String](0) + i.getArgument[Int](1)
}
```
We can now write: (this may be nothing new for users of 2.12, but at least now the API is consistent for both 2.11 and 2.12)
We can now write:
```scala
when(myMock.foo("bar", 42)) thenAnswer ((i: InvocationOnMock) => i.getArgument[String](0) + i.getArgument[Int](1))
```
Expand Down Expand Up @@ -797,13 +800,6 @@ your build.sbt and that warning will be ignored for your tests **only**
matchers usage then you have to explicitly provide the type for the matcher, thus `any` would become `any[MyType]` and
`*` would become `*[MyType]` (you can also use `anyShort`, `anyInt`, etc for the primitive types)

### 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 `ArgumentMatcher[T]` for methods with by-name parameters (they'll throw an exception if used with `ArgumentMatcher[T]`)

## Authors

* **Bruno Bonanno** - *Initial work* - [bbonanno](https://github.com/bbonanno)
Expand Down
10 changes: 5 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ lazy val commonSettings =
Seq(
organization := "org.mockito",
// Load version from the file so that Gradle/Shipkit and SBT use the same version
crossScalaVersions := Seq(currentScalaVersion, "2.12.20", "2.11.12"),
crossScalaVersions := Seq(currentScalaVersion, "2.12.20"),
scalafmtOnCompile := true,
scalacOptions ++= Seq(
"-unchecked",
Expand Down Expand Up @@ -122,8 +122,8 @@ lazy val cats = (project in file("cats"))
commonSettings,
publishSettings,
libraryDependencies ++= Seq(
Dependencies.cats.value,
Dependencies.catsLaws.value % "test",
Dependencies.cats,
Dependencies.catsLaws % "test",
Dependencies.disciplineScalatest % "test",
Dependencies.scalatest % "test"
)
Expand All @@ -150,8 +150,8 @@ lazy val common = (project in file("common"))
noPublishingSettings,
libraryDependencies ++= Dependencies.commonLibraries ++
Dependencies.scalaReflection.value ++ Seq(
Dependencies.catsLaws.value % "test",
Dependencies.scalacheck.value % "test"
Dependencies.catsLaws % "test",
Dependencies.scalacheck % "test"
)
)

Expand Down
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -XX:+CMSClassUnloadingEnabled -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean +test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish'
java -Xms512M -Xmx2G -Xss2M -XX:ReservedCodeCacheSize=192m -Dfile.encoding=UTF-8 -jar sbt/sbt-launch.jar -Dsbt.parser.simple=true clean +test 'set every publishTo := Some(Resolver.file("local-repo", file("target/dist")))' '+ publish'

This file was deleted.

This file was deleted.

136 changes: 84 additions & 52 deletions common/src/main/scala/org/mockito/ReflectionUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ object ReflectionUtils {
import scala.reflect.runtime.{ universe => ru }
import ru._

private val JavaVersion: Int =
System.getProperty("java.version").split("\\.") match {
case Array("1", v, _*) => v.toInt // Java 8 style: 1.8.x
case Array(v, _*) => v.toInt // Java 9+ style: 11.x, 17.x, etc.
}

implicit def symbolToMethodSymbol(sym: Symbol): Symbols#MethodSymbol = sym.asInstanceOf[Symbols#MethodSymbol]

private val mirror = runtimeMirror(getClass.getClassLoader)
Expand Down Expand Up @@ -51,29 +57,23 @@ object ReflectionUtils {
def returnsValueClass: Boolean = findTypeSymbol.exists(_.returnType.typeSymbol.isDerivedValueClass)

private def resolveWithScalaGenerics: Option[Class[_]] =
scala.util
.Try {
findTypeSymbol
.filter(_.returnType.typeSymbol.isClass)
.map(_.asMethod.returnType.typeSymbol.asClass)
.map(mirror.runtimeClass)
}
.toOption
.flatten
uTry {
findTypeSymbol
.filter(_.returnType.typeSymbol.isClass)
.map(_.asMethod.returnType.typeSymbol.asClass)
.map(mirror.runtimeClass)
}.toOption.flatten

private def findTypeSymbol =
scala.util
.Try {
mirror
.classSymbol(method.getDeclaringClass)
.info
.decls
.collectFirst {
case symbol if isNonConstructorMethod(symbol) && customMirror.methodToJava(symbol) === method => symbol
}
}
.toOption
.flatten
uTry {
mirror
.classSymbol(method.getDeclaringClass)
.info
.decls
.collectFirst {
case symbol if isNonConstructorMethod(symbol) && customMirror.methodToJava(symbol) === method => symbol
}
}.toOption.flatten

private def resolveWithJavaGenerics: Option[Class[_]] =
try Some(GenericsResolver.resolve(invocation.getMock.getClass).`type`(method.getDeclaringClass).method(method).resolveReturnClass())
Expand All @@ -85,45 +85,47 @@ object ReflectionUtils {
private def isNonConstructorMethod(d: ru.Symbol): Boolean = d.isMethod && !d.isConstructor

def extraInterfaces[T](implicit $wtt: WeakTypeTag[T], $ct: ClassTag[T]): List[Class[_]] =
scala.util
.Try {
val cls = clazz($ct)
$wtt.tpe match {
case RefinedType(types, _) =>
types.map($wtt.mirror.runtimeClass).collect {
case c: Class[_] if c.isInterface && c != cls => c
}
case _ => List.empty
}
uTry {
val cls = clazz($ct)
$wtt.tpe match {
case RefinedType(types, _) =>
types.map($wtt.mirror.runtimeClass).collect {
case c: Class[_] if c.isInterface && c != cls => c
}
case _ => List.empty
}
.toOption
}.toOption
.getOrElse(List.empty)

def methodsWithLazyOrVarArgs(classes: Seq[Class[_]]): Seq[(Method, Set[Int])] =
classes.flatMap { clazz =>
scala.util
.Try {
mirror
.classSymbol(clazz)
.info
.members
.collect {
case symbol if isNonConstructorMethod(symbol) =>
symbol -> symbol.typeSignature.paramLists.flatten.zipWithIndex.collect {
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
case (p, idx) if p.typeSignature.toString.endsWith("*") => idx
}.toSet
}
.collect {
case (symbol, indices) if indices.nonEmpty => customMirror.methodToJava(symbol) -> indices
}
.toSeq
}
.toOption
uTry {
mirror
.classSymbol(clazz)
.info
.members
.collect {
case symbol if isNonConstructorMethod(symbol) =>
symbol -> symbol.typeSignature.paramLists.flatten.zipWithIndex.collect {
case (p, idx) if p.typeSignature.toString.startsWith("=>") => idx
case (p, idx) if p.typeSignature.toString.endsWith("*") => idx
}.toSet
}
.collect {
case (symbol, indices) if indices.nonEmpty => customMirror.methodToJava(symbol) -> indices
}
.toSeq
}.toOption
.getOrElse(Seq.empty)
}

def setFinalStatic(field: Field, newValue: Any): Unit = {
def setFinalStatic(field: Field, newValue: AnyRef): Unit =
if (JavaVersion < 17)
setFinalStatic17Minus(field, newValue)
else
setFinalStatic17Plus(field, newValue)

private def setFinalStatic17Minus(field: Field, newValue: AnyRef): Unit = {
val clazz = classOf[java.lang.Class[_]]
field.setAccessible(true)
val modifiersField: Field = uTry(clazz.getDeclaredField("modifiers")) match {
Expand All @@ -150,4 +152,34 @@ object ReflectionUtils {
field.set(null, newValue)
}

private def setFinalStatic17Plus(field: Field, newValue: AnyRef): Unit =
try {
// Try to get Unsafe instance (works with both sun.misc.Unsafe and jdk.internal.misc.Unsafe)
val unsafeClass: Class[_] =
try
Class.forName("sun.misc.Unsafe")
catch {
case _: ClassNotFoundException => Class.forName("jdk.internal.misc.Unsafe")
}

val unsafeField = unsafeClass.getDeclaredField("theUnsafe")
unsafeField.setAccessible(true)
val unsafe = unsafeField.get(null)

// Get methods via reflection to handle both Unsafe implementations
val staticFieldBaseMethod = unsafeClass.getMethod("staticFieldBase", classOf[Field])
val staticFieldOffsetMethod = unsafeClass.getMethod("staticFieldOffset", classOf[Field])
val putObjectMethod = unsafeClass.getMethod("putObject", classOf[Object], classOf[Long], classOf[Object])

// Get base and offset for the field
val base: Object = staticFieldBaseMethod.invoke(unsafe, field)
val offset: Long = staticFieldOffsetMethod.invoke(unsafe, field).asInstanceOf[Long]

// Set the field value directly
putObjectMethod.invoke(unsafe, base, java.lang.Long.valueOf(offset), newValue)
} catch {
case NonFatal(e) =>
throw new IllegalStateException(s"Cannot modify final field ${field.getName}", e)
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,4 @@ trait EqMatchers_VersionSpecific {
value
}

/**
* It was intended to be used instead of eqTo when the argument is a value class, but eqTo now supports value classes so it is not needed anymore
*/
@deprecated("Use 'eqTo' instead", since = "1.0.2")
def eqToVal[T: Equality: ValueClassExtractor](value: T)(implicit $pt: Prettifier): T = eqTo(value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,4 @@ trait EqMatchers_VersionSpecific {
value
}

/**
* It was intended to be used instead of eqTo when the argument is a value class, but eqTo now supports value classes so it is not needed anymore
*/
@deprecated("Use 'eqTo' instead", since = "1.0.2")
def eqToVal[T: Equality: ValueClassExtractor](value: T)(implicit $pt: Prettifier): T = eqTo(value)
}
5 changes: 5 additions & 0 deletions core/src/main/scala/org/mockito/captor/ArgCaptor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.mockito.captor

object ArgCaptor {
def apply[T](implicit c: Captor[T]): Captor[T] = c
}
10 changes: 0 additions & 10 deletions core/src/main/scala/org/mockito/captor/Captors.scala

This file was deleted.

This file was deleted.

Loading