Last active
April 27, 2021 07:42
-
-
Save lloydmeta/e4ba5af6dae8f1c68efc6458f0a5879d to your computer and use it in GitHub Desktop.
Scala `AnyVal`-aware helper method around Mockito's `any`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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