-
-
Save ittaiz/c775c382d6b53f74b6e3cd53d9169127 to your computer and use it in GitHub Desktop.
class FruitTest extends SpecificationWithJUnit { | |
trait Fruit | |
case class Apple(color: String) extends Fruit | |
case class Peach(ripeness: Double) extends Fruit | |
def aColorfulApple(color: String): Matcher[Fruit] = { | |
val appleMatcher: Matcher[Apple] = be_===(color) ^^ { a: Apple => | |
a.color aka "apple color" | |
} | |
val fruitMatcher: Matcher[Fruit] = (_: Fruit) match { | |
case apple: Apple => appleMatcher.apply(MustExpectable[Apple](apple)) | |
case _ => ko("something which is not an apple") | |
} | |
fruitMatcher | |
} | |
"match an apple from a set of fruits" in { | |
Set(Apple("red"), Apple("green"), Peach(0.3)) must contain(aColorfulApple("green")) | |
} | |
} |
because we have a lot of polymorphic collections (event streams.. duh), so it's nicer to have event matchers less abstract and to have isA
as generic kind of "adapter".
and if have def aColorfulApple(color: String): Matcher[Fruit]
, it can be akward to re-use on concrete values (not in collections), i.e. this compiles:
Peach(0.3) mustEqual aColorfulApple("green")
Apple("red") mustEqual aColorfulApple("green")
definitely. WDYT about contributing it to specs2? And WDYT about the T subtype of A?
Contributing to spec2:
Yeah, why not :) Just I personally would prefer syntax like this
class FruitTest extends SpecWithJUnit with Matchers {
trait Fruit
case class Apple(color: String) extends Fruit
case class Peach(ripeness: Double) extends Fruit
implicit class IsA[T: ClassTag](matcher: Matcher[T]) {
def unary_~[A] = beLike[A] {
case value: T => matcher(value)
case value => MatchFailure("", "is not " + classTag[T].runtimeClass.getSimpleName, value)
}
}
def aColorfulApple(color: String): Matcher[Apple] =
be_===(color) ^^ ((_: Apple).color aka "color")
"match an apple from a set of fruits" in {
Set(Apple("red"), Apple("green"), Peach(0.3)) must contain(~aColorfulApple("greenz"))
}
}
But not everyone likes this :)
T subtype of A:
If you thinking about having def isA[A <: T, T: ClassTag](matcher: Matcher[T]): Matcher[A]
- it doesn't compile and i'm not sure how to make it work :)
Error:(25, 67) type mismatch;
found : org.specs2.matcher.ValueCheck[FruitTest.this.Apple]
required: org.specs2.matcher.ValueCheck[Product with Serializable with FruitTest.this.Fruit]
Note: FruitTest.this.Apple <: Product with Serializable with FruitTest.this.Fruit, but trait ValueCheck is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
Set(Apple("red"), Apple("green"), Peach(0.3)) must contain(isA(aColorfulApple("greenz")))
Actually given they have the beLike I don't know if the contribution is that dramatic
yup, its just small wrapper for simple:
Set(Apple("red"), Apple("green"), Peach(0.3)) must contain(beLike[Fruit] {
case value: Apple => aColorfulApple("greenz").apply(value)
case _ => ko("not apple")
})
I actually have a very different way of implementing this:
sealed trait Category
case class SpecificCategory(term: String) extends Category
case class CategoryGroup(prefix: String) extends Category
trait SpecificCategoryMatchers {
// Basic category matcher:
def aSpecificCategory: Matcher[SpecificCategory] =
beAnInstanceOf[SpecificCategory]
// Matcher builder as an extended class
implicit class ExtendSpecificCategoryMatcher(base: Matcher[SpecificCategory]) {
def withTerm(termMatcher: Matcher[String]): Matcher[SpecificCategory] =
base and termMatcher ^^ { sc: SpecificCategory => sc.term }
// ... more builders
}
// Two implicit conversions for matching against containers (this is where the subtyping relationship really matters)
/** Enables syntax like `results must contain(aSpecificCategory(...))` */
implicit def `SpecificCategory matcher as ValueCheck`(m: Matcher[SpecificCategory]): ValueCheck[Category] =
matcherIsValueCheck[Category](
beAnInstanceOf[SpecificCategory] and
{ result: Category => result.asInstanceOf[SpecificCategory] } ^^ m
)
/** Enable syntax like `results must contain(aSpecificCategory(...), aSpecificCategory(...), ...)` */
implicit def `Sequence of SpecificCategory matcher as sequence of ValueChecks`(seq: Seq[Matcher[SpecificCategory]]): Seq[ValueCheck[Category]] =
seq map `SpecificCategory matcher as ValueCheck`
}
While this is a bit annoying to write (although you can copy-paste or even template most of this), it allows very flexible syntax on the usage site:
"employ case-insensitive alphabetic ordering" in {
val lowerCase = aCategory("lower")
val upperCase = aCategory("Upper")
assert(lowerCase.term > upperCase.term, "This test case requires a lexicographically-ordered pair")
val index = allocateIndex(aPayload.withCategories(lowerCase, upperCase))
val results = index.suggestCategory("er").results
// This is where the magic happens: results is a Seq[Category] and we're matching on multiple entries
results should contain(exactly(
aSpecificCategory.withTerm("lower"),
aSpecificCategory.withTerm("upper")
))
}
The same principal applies to all of the other subtypes, and you can mix-and-match them safely.
yup, this really looks nice in tests
yup