Skip to content

Instantly share code, notes, and snippets.

@amitayh
Created September 16, 2019 19:33
Show Gist options
  • Save amitayh/d7c0e4d70b00930e6e603dc01e51330f to your computer and use it in GitHub Desktop.
Save amitayh/d7c0e4d70b00930e6e603dc01e51330f to your computer and use it in GitHub Desktop.
Simple example of browser E2E tests using free monad
package example
import cats.effect.IO
import cats.free.Free
import cats.free.Free.liftF
import cats.implicits._
import cats.~>
object BrowserE2E extends App {
// Browser algebra / DSL
sealed trait BrowserA[A]
case class Navigate(url: Url) extends BrowserA[Document]
case class Click(document: Document, point: Point) extends BrowserA[Element]
case class Inspect(document: Document, query: Query) extends BrowserA[Option[Element]]
// "Free" the algebra
type Browser[A] = Free[BrowserA, A]
// Helpers (not required)
def navigate(url: Url): Browser[Document] =
liftF(Navigate(url))
def click(document: Document, point: Point): Browser[Element] =
liftF(Click(document, point))
def inspect(document: Document, query: Query): Browser[Option[Element]] =
liftF(Inspect(document, query))
// Simple test program (just a data structure)
val test: Browser[Boolean] = for {
doc <- navigate(Url("http://localhost:3000"))
_ <- click(doc, Point(100, 100))
header <- inspect(doc, Query("h1#header"))
} yield header match {
case Some(h1) => h1.text == "hello"
case None => false
}
// Interpret each operation in the algebra into an IO
// This is where you actually perform sync / async IO
val interpreter = new (BrowserA ~> IO) {
override def apply[A](fa: BrowserA[A]): IO[A] = fa match {
case Navigate(url) => IO(println(s"Navigating to $url")).as(Document().asInstanceOf[A])
case Click(document, point) => IO(println(s"Clicking on $document at $point")).as(Element().asInstanceOf[A])
case Inspect(document, query) => IO(println(s"Quering $document for $query")).as(Some(Element()).asInstanceOf[A])
}
}
// Run the test with the given interpreter (end of the world)
println(s"Test Result: ${test.foldMap(interpreter).unsafeRunSync()}")
}
// Fake domain objects
case class Url(url: String)
case class Document(/* ... */)
case class Element(/* ... */) {
def text: String = "hello"
}
case class Point(x: Int, y: Int)
case class Query(query: String)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment