Skip to content

Instantly share code, notes, and snippets.

@colomboe
Created December 16, 2019 19:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save colomboe/26a480ebd58130decc6ba8bba5b55272 to your computer and use it in GitHub Desktop.
Save colomboe/26a480ebd58130decc6ba8bba5b55272 to your computer and use it in GitHub Desktop.
Porting of "Simple example of testing with ZIO environment" to Kotlin with KIO
// Porting of https://gist.github.com/jdegoes/dd66656382247dc5b7228fb0f2cb97c8
import it.msec.kio.*
import it.msec.kio.common.tuple.T
import it.msec.kio.common.tuple.T2
import it.msec.kio.common.tuple.T3
import it.msec.kio.ref.Ref
import it.msec.kio.result.Failure
import it.msec.kio.result.Result
import it.msec.kio.result.Success
import it.msec.kio.runtime.RuntimeSuspended
typealias UserID = String
data class UserProfile(val name: String)
// The database module:
interface Database {
// The database module contains the database service:
interface Service {
fun lookup(id: UserID): Task<UserProfile>
fun update(id: UserID, profile: UserProfile): Task<Unit>
}
val database: Service
}
// The logger module:
interface Logger {
// The logger module contains the logger service:
interface Service {
fun info(id: String): Task<Unit>
}
val logger: Service
}
// 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<State>) : Database.Service {
// The database state, which keeps track of the data as well as a log of
// database operations performed against the database:
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): T2<State, UserProfile?> =
T(log("Lookup(${id})"), map[id])
fun update(id: UserID, profile: UserProfile): State =
copy(map = map + (id to profile)).log("Update(${id}, ${profile})")
}
override fun lookup(id: UserID): Task<UserProfile> =
ref.modify { it.lookup(id) }.flatMap { p -> unsafe { p!! } }
override fun update(id: UserID, profile: UserProfile): Task<Unit> =
ref.update { it.update(id, profile) }.map { Unit }
}
// A concurrent-safe test logger service, which uses a `Ref` to keep track
// of log output:
class LoggerTestService(val ref: Ref<List<String>>) : Logger.Service {
override fun info(line: String): Task<Unit> = ref.update { it + line }
}
interface DatabaseWithLogger : Database, Logger
// 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 <E, A> testScenario(state: DatabaseTestService.State, eff: KIO<DatabaseWithLogger, E, A>): UIO<T3<Result<E, A>, DatabaseTestService.State, List<String>>> {
val databaseRef = Ref(state)
val loggerRef = Ref(emptyList<String>())
val env = object : DatabaseWithLogger {
override val database: Database.Service = DatabaseTestService(databaseRef)
override val logger: Logger.Service = LoggerTestService(loggerRef)
}
return eff.provide(env)
.fold(::Failure, ::Success)
.flatMapT { databaseRef.get() }
.flatMapT { loggerRef.get() }
}
// An example program that uses database and logger modules:
fun <R> lookedUpProfile(): RIO<R, UserProfile> where R : Database, R : Logger = ask {
with(it) {
database.lookup("abc")
.peek { profile -> logger.info(profile.name) }
}
}
// Running a test scenario and unsafely executing it to see what happens:
fun main() {
val initialState = DatabaseTestService.State(mapOf("abc" to UserProfile("testName")), emptyList())
val v = testScenario(initialState, lookedUpProfile())
val result = RuntimeSuspended.unsafeRunSync(v)
println(result)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment