Skip to content

Instantly share code, notes, and snippets.

@lloydmeta
Last active April 27, 2021 07:42
Show Gist options
  • Save lloydmeta/e4ba5af6dae8f1c68efc6458f0a5879d to your computer and use it in GitHub Desktop.
Save lloydmeta/e4ba5af6dae8f1c68efc6458f0a5879d to your computer and use it in GitHub Desktop.
Scala `AnyVal`-aware helper method around Mockito's `any`
import scala.reflect.macros.whitebox
import scala.language.experimental.macros
trait AnyValAwareMatchers {
/**
* An `any` matcher implementation macro that is aware of [[AnyVal]] wrapper classes.
*
* When using it with custom [[AnyVal]] classes, make sure to pass the concrete type.
*
* Implemented as a separate method because there are some edge cases involving type inference where certain
* mocking/stubbing don't work well with it.
*/
def anyVal[T]: T = macro AnyValAwareMatchersMacro.anyValImpl[T]
}
object AnyValAwareMatchers extends AnyValAwareMatchers
object AnyValAwareMatchersMacro {
def anyValImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[T] = {
import c.universe._
val tpe = weakTypeOf[T]
val typeSymbol = tpe.typeSymbol
val isValueClass = typeSymbol.isClass && typeSymbol.asClass.isDerivedValueClass
val r = c.Expr[T] {
val tree = if (isValueClass) {
val innerType =
tpe.members.filter(_.isConstructor).flatMap(_.asMethod.paramLists).flatMap(_.map(_.typeSignature)).head
q"""(_root_.org.mockito.ArgumentMatchers.any(): $innerType).asInstanceOf[$tpe]"""
} else {
q"""_root_.org.mockito.ArgumentMatchers.any()"""
}
tree
}
r
}
}
// Tests in AnyValAwareMatchersSpec.scala somewhere
class AnyValAwareMatchersSpec extends AnyFunSpec with Diagrams with MockitoSugar with AnyValAwareMatchers {
import AnyValAwareMatchersSpec._
describe("#anyVal") {
it("should work with AnyVal types that have public and private constructor methods as well as non-AnyVal types") {
val greeter = mock[Greeter]
Mockito.when(greeter.greet(anyVal[UserId], anyVal[Greeting], anyVal, anyVal)).thenReturn(true)
assert(greeter.greet(UserId("test"), Greeting.build("high"), 3, new Thing("")))
}
}
}
object AnyValAwareMatchersSpec {
trait Greeter {
def greet(user: UserId, greeting: Greeting, num: Int, normalClass: Thing): Boolean
}
final case class UserId(value: String) extends AnyVal
final class Greeting private (val value: String) extends AnyVal
final class Thing(val value: String)
object Greeting {
def build(v: String): Greeting = new Greeting(v)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment