Last active
October 13, 2024 16:17
-
-
Save jdegoes/dd66656382247dc5b7228fb0f2cb97c8 to your computer and use it in GitHub Desktop.
Simple example of testing with ZIO environment
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
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") | |
_ <- 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) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment