Skip to content

Instantly share code, notes, and snippets.

@colomboe
Created July 4, 2019 19:25
Show Gist options
  • Save colomboe/20f14cb1c42dd823095d1cd7022186af to your computer and use it in GitHub Desktop.
Save colomboe/20f14cb1c42dd823095d1cd7022186af 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)
}
@nomisRev
Copy link

nomisRev commented Jul 4, 2019

It's the suspend version of raiseError. It's like IO.effect { none<Int>().getOrElse { throw RuntimeException("none") } }, it would look nicer with BIO tho.

@colomboe
Copy link
Author

colomboe commented Jul 5, 2019

You're right, I modelled it just following the Scala code, in a real implementation I'd model it as an Option.

@fcabouat
Copy link

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,

@colomboe
Copy link
Author

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.

@fcabouat
Copy link

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