-
-
Save lenguyenthanh/60d2e6421bb94a78dd1250f51975677a to your computer and use it in GitHub Desktop.
Porting of "Simple example of testing with ZIO environment" to Kotlin
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
// Porting of https://gist.github.com/jdegoes/dd66656382247dc5b7228fb0f2cb97c8 | |
typealias UserID = String | |
data class UserProfile(val name: String) | |
// The database module: | |
interface DatabaseService { | |
suspend fun dbLookup(id: UserID): UserProfile | |
suspend fun dbUpdate(id: UserID, profile: UserProfile) | |
} | |
// The logger module: | |
interface LoggerService { | |
suspend fun logInfo(id: String) | |
} | |
// A concurrent-safe test database service, which uses a `Ref` to keep track | |
// of changes to the test database state: | |
class DatabaseTestService(val ref: Ref<ForIO, State>) : DatabaseService { | |
data class State(val map: Map<UserID, UserProfile>, val ops: List<String>) { | |
fun log(op: String): State = copy(ops = listOf(op) + ops) | |
fun lookup(id: UserID): Tuple2<State, Option<UserProfile>> = | |
Tuple2(log("Lookup($id)"), map.getOption(id)) | |
fun update(id: UserID, profile: UserProfile): State = | |
copy(map = map.plus(id to profile)).log("Update($id, $profile)") | |
} | |
override suspend fun dbLookup(id: UserID): UserProfile = | |
ref.modify { it.lookup(id) }.fix().suspended().orNull()!! | |
override suspend fun dbUpdate(id: UserID, profile: UserProfile) = | |
ref.update { it.update(id, profile) }.fix().suspended() | |
} | |
// A concurrent-safe test logger service, which uses a `Ref` to keep track | |
// of log output: | |
class LoggerTestService(val ref: Ref<ForIO, List<String>>) : LoggerService { | |
override suspend fun logInfo(line: String) = ref.update { it.plus(line) }.fix().suspended() | |
} | |
interface Env : DatabaseService, LoggerService | |
// A helper function to run a test scenario, and extract out test data. | |
// This function can be used many times across many unit tests. | |
fun <A> testScenario(state: State, program: suspend Env.() -> A) : IO<Tuple3<Either<Throwable, A>, State, List<String>>> = | |
IO.fx { | |
val databaseRef = !ref { state } | |
val loggerRef = !ref { emptyList<String>() } | |
val env: Env = object : Env, | |
DatabaseService by DatabaseTestService(databaseRef), | |
LoggerService by LoggerTestService(loggerRef) {} | |
val either = !attempt { env.program() } | |
val dbState = !databaseRef.get() | |
val loggerState = !loggerRef.get() | |
Tuple3(either, dbState, loggerState) | |
} | |
// An example program that uses database and logger modules: | |
suspend fun Env.lookedUpProfile(): UserProfile { | |
val profile = dbLookup("abc") | |
logInfo(profile.name) | |
return profile | |
} | |
// Running a test scenario and unsafely executing it to see what happens: | |
fun main() { | |
val program = testScenario(State(mapOf("abc" to UserProfile("testName")), emptyList()), Env::lookedUpProfile) | |
val result = program.unsafeRunSync() | |
println(result) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment