Skip to content

Instantly share code, notes, and snippets.

Last active January 6, 2023 14:08
  • Star 25 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
Simple example of testing with ZIO environment
object test {
import scalaz.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")
_ <-
} 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 {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment