Created
July 4, 2019 19:25
-
-
Save colomboe/20f14cb1c42dd823095d1cd7022186af 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) | |
} |
I can't manage to make this run with arrow 0.10.3
I was able to make some parts type check again like :
val databaseRef = !Ref(state)
val loggerRef = !Ref(emptyList<String>())
But I've still some errors. Would it be possible to post a fully runnable example ?
Cheers,
I'm waiting for an API freeze before updating my Arrow examples, since the API in changing very fast in every release. But I'll try to update this if I find some time to work on it.
In the meanwhile, here is the same example with KIO bifunctor, that is much more similar to ZIO:
https://gist.github.com/colomboe/26a480ebd58130decc6ba8bba5b55272
In the future also Arrow should have something similar.
Thank you very much for taking the time to answer. It is indeed more similar / and a bit more straightforward. I'll try experimenting with it :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You're right, I modelled it just following the Scala code, in a real implementation I'd model it as an Option.