Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save iamanandkris/6db329d8638ef278c1bf8db5351d0a2c to your computer and use it in GitHub Desktop.
Save iamanandkris/6db329d8638ef278c1bf8db5351d0a2c to your computer and use it in GitHub Desktop.
import scalaz.zio._
type UserID = String
case class UserProfile(name: String)
//Companion object for below Database Module.
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 database module, its like a Free algebra definition
trait Database {
val database: Database.Service
}
//Companion for below Logger Module
object Logger {
// The logger module contains the logger service:
trait Service {
def info(id: String): Task[Unit]
}
}
// The logger module:
trait Logger {
def logger: Logger.Service
}
//Base trait for all the operations that are to be recorded when running test
trait Operation
//The TestState
final case class ExecutedInstructions(ops: List[Operation])
// A concurrent-safe test database service, which uses a `Ref` to keep track
// of changes to the instruction set:
class DatabaseTestService(ref: Ref[ExecutedInstructions]) extends Database.Service {
private var map: Map[UserID, UserProfile] = Map("abc" -> UserProfile("testName"))
def lookup(id: UserID): Task[UserProfile] =
ref.update(x => x.copy(ops = x.ops :+ DatabaseTestService.UserLookedUp(id))).map(x => map(id))
def update(id: UserID, profile: UserProfile): Task[Unit] =
ref.update(x => x.copy(ops = x.ops :+DatabaseTestService.UserUpdated(id, profile))).map(x => map = map + (id -> profile))
}
object DatabaseTestService {
// database operations performed against the database:
trait DatabaseOperation extends Operation
case class UserUpdated(id:UserID, profile:UserProfile) extends DatabaseOperation
case class UserLookedUp(id:UserID) extends DatabaseOperation
}
// A concurrent-safe test logger service, which uses a `Ref` to keep track
// of changes to the instruction set:
class LoggerTestService(ref: Ref[ExecutedInstructions]) extends Logger.Service {
def info(line: String): Task[Unit] =
ref.update(x => x.copy(ops = x.ops :+ LoggerTestService.MessageLogged(line))).map(x => ())
}
object LoggerTestService{
trait LoggerOperation extends Operation
case class MessageLogged(message:String) extends LoggerOperation
}
// 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](initialState: ExecutedInstructions)
(eff: ZIO[Database with Logger, E, A]): UIO[(Either[E, A], ExecutedInstructions)] = {
for {
instructionRef <- Ref.make(initialState)
// Construct a new environment for the effect being tested:
env = new Database with Logger {
val database = new DatabaseTestService(instructionRef)
val logger = new LoggerTestService(instructionRef)
}
either <- eff.provide(env).either
instructionState <- instructionRef.get
} yield (either, instructionState)
}
// An example program that uses database and logger modules. This is the actual business logic to execute:
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(ExecutedInstructions(Nil))(lookedUpProfile)
val runtime = new DefaultRuntime {}
runtime.unsafeRun(v) //Result - (Right(UserProfile(testName)),ExecutedInstructions(List(UserLookedUp(abc), MessageLogged(testName))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment