Last active
October 14, 2019 13:25
-
-
Save Dnomyar/42e95c6911ca5c09c6aafe871df420b4 to your computer and use it in GitHub Desktop.
Real life law checking - https://www.iteratorshq.com/blog/tagless-with-discipline-testing-scala-code-the-right-way/
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
package example | |
import cats.effect.{IO, Sync} | |
import cats.kernel.laws._ | |
import cats.kernel.laws.discipline._ | |
import cats.{Eq, Monad} | |
import org.scalacheck.Gen._ | |
import org.scalacheck.Prop._ | |
import org.scalacheck._ | |
import org.scalatest._ | |
import org.scalatest.flatspec.AnyFlatSpec | |
import org.typelevel.discipline.Laws | |
import org.typelevel.discipline.scalatest.Discipline | |
import scala.language.higherKinds | |
case class User(id: String) | |
trait UserService[F[_]] { | |
def save(user: User): F[User] | |
def getUser(id: String): F[Option[User]] | |
def delete(user: User): F[Boolean] | |
} | |
object UserService { | |
// def service[F[_]]: UserService[F] = new UserService[F] { | |
def service[F[_]](implicit F: Sync[F]): UserService[F] = new UserService[F] { | |
override def save(user: User): F[User] = F.pure(User("id")) | |
override def getUser(id: String): F[Option[User]] = F.pure(Some(User("iddd"))) | |
override def delete(user: User): F[Boolean] = F.pure(true) | |
} | |
} | |
trait UserServiceLaws[F[_]] { | |
def algebra: UserService[F] | |
implicit def M: Monad[F] | |
import cats.syntax.flatMap._ | |
def saveGetComposition(user: User): IsEq[F[Option[User]]] = | |
algebra.save(user) >> algebra.getUser(user.id) <-> M.pure(Some(user)) | |
} | |
object UserServiceLaws { | |
def apply[F[_]](instance: UserService[F])(implicit ev: Monad[F]): UserServiceLaws[F] = | |
new UserServiceLaws[F] { | |
override val algebra = instance | |
override implicit val M: Monad[F] = ev | |
} | |
} | |
trait UserServiceAlgebraTests[F[_]] extends Laws { | |
def laws: UserServiceLaws[F] | |
def algebra(implicit arbUserService: Arbitrary[User], | |
eqFOptUserService: Eq[F[Option[User]]]) = | |
new SimpleRuleSet( | |
name = "UserServices", | |
"find and get compose" -> forAll(laws.saveGetComposition _) | |
) | |
} | |
object UserServiceAlgebraTests { | |
def apply[F[_] : Monad](instance: UserService[F]): UserServiceAlgebraTests[F] = new UserServiceAlgebraTests[F] { | |
override val laws: UserServiceLaws[F] = UserServiceLaws(instance) | |
} | |
} | |
class BinarySearchTreeSpec | |
extends AnyFlatSpec | |
with Matchers | |
with Discipline { | |
final val UserGen: Gen[User] = (for { | |
id <- nonEmptyListOf(alphaNumChar).map(_.mkString) | |
} yield id) suchThat (_.length <= 254) map User | |
implicit final val ArbitraryEmail: Arbitrary[User] = Arbitrary(UserGen) | |
implicit val eqUser: Eq[User] = new Eq[User] { | |
override def eqv(x: User, y: User): Boolean = x == y | |
} | |
implicit def eqOption[A](implicit aEq: Eq[A]): Eq[Option[A]] = new Eq[Option[A]] { | |
override def eqv(x: Option[A], y: Option[A]): Boolean = { | |
for{ | |
f1 <- x | |
f2 <- y | |
} yield f1 === f2 | |
}.getOrElse(false) | |
} | |
implicit def eqIO[A](implicit aEq: Eq[A]): Eq[IO[A]] = new Eq[IO[A]] { | |
override def eqv(x: IO[A], y: IO[A]): Boolean = { | |
(x.unsafeRunSync, y.unsafeRunSync) match { | |
case (xx, yy) => | |
println(s"$xx == $yy = ${xx == yy}") | |
xx == yy | |
} | |
} | |
} | |
checkAll("UserService", UserServiceAlgebraTests(UserService.service[IO]).algebra) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment