Skip to content

Instantly share code, notes, and snippets.

@jdegoes
Last active October 13, 2024 16:17
Show Gist options
  • Select an option

  • Save jdegoes/dd66656382247dc5b7228fb0f2cb97c8 to your computer and use it in GitHub Desktop.

Select an option

Save jdegoes/dd66656382247dc5b7228fb0f2cb97c8 to your computer and use it in GitHub Desktop.

Revisions

  1. jdegoes revised this gist Jul 4, 2019. 1 changed file with 21 additions and 16 deletions.
    37 changes: 21 additions & 16 deletions zio-test.scala
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,9 @@
    import zio._

    object test {
    import scalaz.zio._

    type UserID = String
    case class UserProfile(name:String)
    case class UserProfile(name: String)

    // The database module:
    trait Database {
    @@ -26,38 +28,40 @@
    }
    }

    // A concurrent-safe test database service, which uses a `Ref` to keep track
    // A concurrent-safe test database service, which uses a `Ref` to keep track
    // of changes to the test database state:
    class DatabaseTestService(ref: Ref[DatabaseTestService.State]) extends Database.Service {
    def lookup(id: UserID): Task[UserProfile] =
    def lookup(id: UserID): Task[UserProfile] =
    ref.modify(_.lookup(id)).flatMap(option => Task(option.get))

    def update(id: UserID, profile: UserProfile): Task[Unit] =
    ref.update(_.update(id, profile)).unit
    }
    object DatabaseTestService {
    // The database state, which keeps track of the data as well as a log of
    // The database state, which keeps track of the data as well as a log of
    // database operations performed against the database:
    final case class State(map: Map[UserID, UserProfile], ops: List[String]) {
    def log(op: String): State = copy(ops = op :: ops)
    def lookup(id: UserID): (Option[UserProfile], State) =

    def lookup(id: UserID): (Option[UserProfile], State) =
    (map.get(id), log(s"Lookup(${id})"))
    def update(id: UserID, profile: UserProfile): State =

    def update(id: UserID, profile: UserProfile): State =
    copy(map = map + (id -> profile)).log(s"Update(${id}, ${profile})")
    }
    }

    // A concurrent-safe test logger service, which uses a `Ref` to keep track
    // A concurrent-safe test logger service, which uses a `Ref` to keep track
    // of log output:
    class LoggerTestService(ref: Ref[Vector[String]]) extends Logger.Service {
    def info(line: String): Task[Unit] = ref.update(_ :+ line).unit
    }

    // A helper function to run a test scenario, and extract out test data.
    // A helper function to run a test scenario, and extract out test data.
    // This function can be used many times across many unit tests.
    def testScenario[E, A](state: DatabaseTestService.State)(eff: ZIO[Database with Logger, E, A]): UIO[(Either[E, A], DatabaseTestService.State, Vector[String])] =
    def testScenario[E, A](
    state: DatabaseTestService.State
    )(eff: ZIO[Database with Logger, E, A]): UIO[(Either[E, A], DatabaseTestService.State, Vector[String])] =
    for {
    databaseRef <- Ref.make(state)
    loggerRef <- Ref.make(Vector.empty[String])
    @@ -66,16 +70,16 @@
    val database = new DatabaseTestService(databaseRef)
    val logger = new LoggerTestService(loggerRef)
    }
    either <- eff.provide(env).either
    either <- eff.provide(env).either
    dbState <- databaseRef.get
    loggerState <- loggerRef.get
    } yield (either, dbState, loggerState)

    // An example program that uses database and logger modules:
    val lookedUpProfile: ZIO[Database with Logger, Throwable, UserProfile] =
    val lookedUpProfile: ZIO[Database with Logger, Throwable, UserProfile] =
    ZIO.accessM[Logger with Database] { modules =>
    import modules.database
    import modules.logger
    import modules.logger

    for {
    profile <- database.lookup("abc")
    @@ -87,4 +91,5 @@
    val v = testScenario(DatabaseTestService.State(Map("abc" -> UserProfile("testName")), Nil))(lookedUpProfile)

    val runtime = new DefaultRuntime {}
    runtime.unsafeRun(v)
    runtime.unsafeRun(v)
    }
  2. jdegoes created this gist Jul 4, 2019.
    90 changes: 90 additions & 0 deletions zio-test.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,90 @@
    import zio._

    type UserID = String
    case class UserProfile(name:String)

    // The database module:
    trait Database {
    val database: Database.Service
    }
    object Database {
    // The database module contains the database service:
    trait Service {
    def lookup(id: UserID): Task[UserProfile]
    def update(id: UserID, profile: UserProfile): Task[Unit]
    }
    }

    // The logger module:
    trait Logger {
    def logger: Logger.Service
    }
    object Logger {
    // The logger module contains the logger service:
    trait Service {
    def info(id: String): Task[Unit]
    }
    }

    // A concurrent-safe test database service, which uses a `Ref` to keep track
    // of changes to the test database state:
    class DatabaseTestService(ref: Ref[DatabaseTestService.State]) extends Database.Service {
    def lookup(id: UserID): Task[UserProfile] =
    ref.modify(_.lookup(id)).flatMap(option => Task(option.get))

    def update(id: UserID, profile: UserProfile): Task[Unit] =
    ref.update(_.update(id, profile)).unit
    }
    object DatabaseTestService {
    // The database state, which keeps track of the data as well as a log of
    // database operations performed against the database:
    final case class State(map: Map[UserID, UserProfile], ops: List[String]) {
    def log(op: String): State = copy(ops = op :: ops)

    def lookup(id: UserID): (Option[UserProfile], State) =
    (map.get(id), log(s"Lookup(${id})"))

    def update(id: UserID, profile: UserProfile): State =
    copy(map = map + (id -> profile)).log(s"Update(${id}, ${profile})")
    }
    }

    // A concurrent-safe test logger service, which uses a `Ref` to keep track
    // of log output:
    class LoggerTestService(ref: Ref[Vector[String]]) extends Logger.Service {
    def info(line: String): Task[Unit] = ref.update(_ :+ line).unit
    }

    // A helper function to run a test scenario, and extract out test data.
    // This function can be used many times across many unit tests.
    def testScenario[E, A](state: DatabaseTestService.State)(eff: ZIO[Database with Logger, E, A]): UIO[(Either[E, A], DatabaseTestService.State, Vector[String])] =
    for {
    databaseRef <- Ref.make(state)
    loggerRef <- Ref.make(Vector.empty[String])
    // Construct a new environment for the effect being tested:
    env = new Database with Logger {
    val database = new DatabaseTestService(databaseRef)
    val logger = new LoggerTestService(loggerRef)
    }
    either <- eff.provide(env).either
    dbState <- databaseRef.get
    loggerState <- loggerRef.get
    } yield (either, dbState, loggerState)

    // An example program that uses database and logger modules:
    val lookedUpProfile: ZIO[Database with Logger, Throwable, UserProfile] =
    ZIO.accessM[Logger with Database] { modules =>
    import modules.database
    import modules.logger

    for {
    profile <- database.lookup("abc")
    _ <- logger.info(profile.name)
    } yield profile
    }

    // Running a test scenario and unsafely executing it to see what happens:
    val v = testScenario(DatabaseTestService.State(Map("abc" -> UserProfile("testName")), Nil))(lookedUpProfile)

    val runtime = new DefaultRuntime {}
    runtime.unsafeRun(v)