Skip to content

Instantly share code, notes, and snippets.

@DamianReeves
Last active December 4, 2017 17:29
Show Gist options
  • Save DamianReeves/2994e9f05a74af97f0a66ba440af388b to your computer and use it in GitHub Desktop.
Save DamianReeves/2994e9f05a74af97f0a66ba440af388b to your computer and use it in GitHub Desktop.
Shapeless Expectations
package experiments
import experiments.expectation.EquivalenceExpectationResult
import shapeless._
trait EquivalenceExpectation[-In] {
type Result
def apply(actual:In, expected:In):Result
}
trait EquivalenceExpectation0 {
implicit def generic[F,G](
implicit
gen: Generic.Aux[F,G],
genExpect: Lazy[EquivalenceExpectation[G]]
): EquivalenceExpectation.Aux[F, genExpect.value.Result] = new EquivalenceExpectation[F] {
type Result = genExpect.value.Result
def apply(actual:F, expected:F):Result = genExpect.value.apply(
gen.to(actual),
gen.to(expected)
)
}
}
object EquivalenceExpectation extends EquivalenceExpectation0 {
type Expectation[In] = EquivalenceExpectation[In]
type ExpectationResult[In] = EquivalenceExpectationResult[In]
import expectation.{Equivalent, NotEquivalent}
def apply[In](implicit expect: Lazy[Expectation[In]]): Aux[In, expect.value.Result] = expect.value
type Aux[In, Result0] = Expectation[In] {type Result = Result0}
implicit val booleanExpectation: EquivalenceExpectation.Aux[Boolean, ExpectationResult[Boolean]] = new Expectation[Boolean] {
type Result = ExpectationResult[Boolean]
def apply(actual: Boolean, expected: Boolean): Result =
if (actual == expected) Equivalent[Boolean](actual, expected)
else NotEquivalent[Boolean](actual, expected)
}
implicit val intExpectation: EquivalenceExpectation.Aux[Int, ExpectationResult[Int]] = new Expectation[Int] {
type Result = ExpectationResult[Int]
def apply(actual: Int, expected: Int): Result =
if (actual == expected) Equivalent[Int](actual, expected)
else NotEquivalent[Int](actual, expected)
}
implicit val stringExpectation: EquivalenceExpectation.Aux[String, ExpectationResult[String]] = new Expectation[String] {
type Result = ExpectationResult[String]
def apply(actual: String, expected: String): Result =
if (actual.equalsIgnoreCase(expected)) Equivalent[String](actual, expected)
else NotEquivalent[String](actual, expected)
}
implicit val bigDecimalExpectation: EquivalenceExpectation.Aux[BigDecimal, ExpectationResult[BigDecimal]] = new Expectation[BigDecimal] {
type Result = ExpectationResult[BigDecimal]
def apply(actual: BigDecimal, expected: BigDecimal): Result =
if (actual == expected) Equivalent[BigDecimal](actual, expected)
else NotEquivalent[BigDecimal](actual, expected)
}
implicit def optionExpectation[T](
implicit expectT: Lazy[Expectation[T]]
): EquivalenceExpectation.Aux[Option[T], ExpectationResult[Option[T]]] = new Expectation[Option[T]] {
type Result = ExpectationResult[Option[T]]
def apply(actual: Option[T], expected: Option[T]): Result = (actual, expected) match {
case (None,None) => NotEquivalent(None,None)
case (Some(a),Some(e)) => expectT.value.apply(a,e) match {
case Equivalent(_, _) => Equivalent(actual, expected)
case NotEquivalent(_,_) => NotEquivalent(actual,expected)
}
case (Some(_), None) => NotEquivalent(actual, expected)
case (None, Some(_)) => NotEquivalent(actual, expected)
}
}
implicit def deriveHNil: EquivalenceExpectation.Aux[HNil, ExpectationResult[HNil]] = new Expectation[HNil] {
type Result = ExpectationResult[HNil]
def apply(actual: HNil, expected: HNil): ExpectationResult[HNil] = Equivalent(HNil, HNil)
}
implicit def derivedHCons[H, T <: HList](
implicit
expectH: Expectation[H],
expectT: Lazy[Expectation[T] { type Result <:HList }]
):EquivalenceExpectation.Aux[H :: T, expectH.Result :: expectT.value.Result] = new Expectation[H :: T]{
type Result = expectH.Result :: expectT.value.Result
def apply(actual:H :: T, expected: H :: T):Result = {
expectH(actual.head, expected.head) :: expectT.value(actual.tail, expected.tail)
}
}
}
package experiments
import org.scalatest.{FlatSpec, Matchers, WordSpec}
import expectation.ExpectationSyntax._
import experiments.EquivalenceExpectationSpecs.Dog
import shapeless._
class EquivalenceExpectationSpecs extends WordSpec with Matchers {
"A Boolean value" when {
"true" should {
"be equivalent to another true value" in {
val result = true.isExpectedToBeEquivalentTo(true)
result shouldBe expectation.Equivalent(true, true)
}
"not be equivalent to false" in {
var result = true.isExpectedToBeEquivalentTo(false)
result shouldBe expectation.NotEquivalent(true, false)
}
}
}
"An Int value" when {
"x" should {
"be equivalent to y when y = x" in {
val x,y = 100
x.isExpectedToBeEquivalentTo(y) shouldBe expectation.Equivalent(x,y)
}
"NOT be equivalent to y when y != x" in {
val x = 100
val y = 200
x.isExpectedToBeEquivalentTo(y) shouldBe expectation.NotEquivalent(x,y)
}
}
}
"An Option value" when {
"Some(inner)" should {
"evaluate as equivalent wben the expectation is checked against the same value" in {
Option("Hello World").isExpectedToBeEquivalentTo(Option("Hello World")) should be (expectation.Equivalent(
Option("Hello World"),
Option("Hello World")
))
}
"evaluate as not equivalent wben the expectation is checked against a different value" in {
Option("Hello Mundo").isExpectedToBeEquivalentTo(Option("Hello World")) should be (expectation.NotEquivalent(
Option("Hello Mundo"),
Option("Hello World")
))
}
}
}
"Given two HList instances" when {
"the HList type is HNil it" should {
val input = HNil
"hold that both values must be equivalent" in {
input.isExpectedToBeEquivalentTo(input) shouldBe expectation.Equivalent(
input,
input
)
}
}
"two equal instances are checked the result" should {
val input = 42::HNil //"See"::"Spot"::"Run"::HNil
"be that they are equivalent" in {
//val exp = implicitly[EquivalenceExpectation[HNil]]
//val result = exp.apply(input, input)
input.isExpectedToBeEquivalentTo(input) shouldBe expectation.Equivalent(
input,
input
)
}
}
}
"Given two instances of a case class" when {
"two equal instances arec checked the result" should {
val spot = Dog("Spot", 5, "run")
val spot2 = Dog("Spot", 5, "run")
"be that they are equivalent" in {
// spot.isExpectedToBeEquivalentTo(spot2) shouldBe expectation.Equivalent(
// spot,
// spot2
// )
}
}
}
}
object EquivalenceExpectationSpecs {
case class Dog(name:String, bones:Int, action:String)
}
package experiments
import shapeless.Lazy
object expectation {
type Check[In] = (In,In) => Boolean
sealed trait ExpectationResultLike
sealed trait ExpectationResult[A]
sealed trait EquivalenceExpectationResult[A] extends ExpectationResult[A]
final case class Equivalent[A](actual:A,expected:A) extends EquivalenceExpectationResult[A]
final case class NotEquivalent[A](actual:A, expected:A) extends EquivalenceExpectationResult[A]
object ExpectationSyntax {
implicit class ExpectationOps[In](val actual: In) extends AnyVal {
def isExpectedToBeEquivalentTo(expected:In)(implicit expect: Lazy[EquivalenceExpectation[In]]): expect.value.Result =
expect.value(actual, expected)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment