Skip to content

Instantly share code, notes, and snippets.

@lenguyenthanh
Forked from colomboe/fx-test.kt
Created July 5, 2019 07:09
Show Gist options
  • Save lenguyenthanh/60d2e6421bb94a78dd1250f51975677a to your computer and use it in GitHub Desktop.
Save lenguyenthanh/60d2e6421bb94a78dd1250f51975677a to your computer and use it in GitHub Desktop.
Porting of "Simple example of testing with ZIO environment" to Kotlin
// 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