dave08
02/21/2023, 1:33 PMdave08
02/21/2023, 1:34 PMsimon.vergauwen
02/21/2023, 1:34 PMLazyMaterialized
comes from Kotest. It's short-hand for install(resource { }.extension())
.simon.vergauwen
02/21/2023, 1:35 PMdave08
02/21/2023, 1:35 PMdave08
02/21/2023, 1:36 PMdave08
02/21/2023, 1:37 PMsimon.vergauwen
02/21/2023, 1:39 PMsuspend fun ResourceScope.hikariDataSource(...): HikariDataSource = TODO()
class MyTest : StringSpec() {
val dataSource = install { hikariDataSource(...) }
}
dave08
02/21/2023, 1:39 PMsimon.vergauwen
02/21/2023, 1:40 PMsuspend fun ResourceScope.hikariDataSource(...): HikariDataSource = TODO()
class MyTest : StringSpec() {
val dataSource = install(resource { hikariDataSource(...) }.extension())
}
dave08
02/21/2023, 1:41 PMsuspend fun hikariDataSource(...): Resource<HikariDataSource> = resource { TODO() }
simon.vergauwen
02/21/2023, 1:42 PMdave08
02/21/2023, 1:44 PMdave08
02/21/2023, 1:45 PMsimon.vergauwen
02/21/2023, 1:53 PMRaise
especially with upcoming context receivers which sadly are not stable yet 😞 But on the other side a lot of people prefer Either
, which can also be useful when interopting with Java or Swift. Either
is also much more widely known, and thus might introduce less friction.dave08
02/21/2023, 1:55 PMsuspend fun foo(): Either<.., Bar> = either { }
// vs.
suspend fun Raise<..>.foo(): Bar = ...
dave08
02/21/2023, 1:56 PMResourceScope....
vs. resource { }
directly in the functionsimon.vergauwen
02/21/2023, 1:56 PMRaise<..>
is not always possible, since you're limited to 1 context receiver.
So for example in the webinar of last week I used suspend fun ResourceScope.postgres(..): Either<Invalid, Postgres>
dave08
02/21/2023, 1:59 PMsuspend fun ResourceScope.hikariDataSource(...): HikariDataSource = TODO()
... I guess that when context recievers will be available, it would be easier to migrate with that...simon.vergauwen
02/21/2023, 2:01 PMbind
or flatMap
necessary to compose different values, simply function invocation is sufficient
• No allocations, albeit that is completely neglectable IMO unless it occurs in some hot loop.dave08
02/21/2023, 2:36 PMfun findRandomOpenPort(): Int {
ServerSocket(0).use { socket -> return socket.localPort }
}
data class WireMockWrapper(
val wm: WireMockServer,
val port: Int,
val client: HttpClient,
)
fun WireMockWrapper(): Resource<WireMockWrapper> = resource {
val port = findRandomOpenPort()
val wm = install({ WireMockServer(options().port(port).notifier(ConsoleNotifier(true))) }, { wm, _ ->
wm.shutdownServer()
})
val client = autoCloseable {
HttpClient {
install(DefaultRequest) { url("<http://localhost>:$port/") }
install(ContentNegotiation) { json() }
}
}
WireMockWrapper(wm, port, client)
}
but when I get to using it in Kotest...dave08
02/21/2023, 2:39 PMclass HandleServiceFailuresTest : FreeSpec() {
init {
val wmw = extension(WireMockWrapper().extension())
// This can't be run w/o runBlocking...
wmw.resource.use {
extension(WireMockListener(it.wm, ListenerMode.PER_TEST))
}
...
on the other hand, using just the resource extension won't reset the endpoints setup before each test... it'll only handle closing the server at the end of the spec.simon.vergauwen
02/21/2023, 2:41 PMLifecycleMode.EveryTest
to extension
,
WireMockWrapper().extension(LifecycleMode.EveryTest)
.dave08
02/21/2023, 2:42 PMsimon.vergauwen
02/21/2023, 2:44 PMWireMockListener
to get reset? That is not a resource in this case?dave08
02/21/2023, 2:57 PMbeforeTest {
wmw.resource.use { it.wm.resetAll() }
}
then the extension would take down the server only at the end of each spec, and reset the connections.
I think I'm starting to understand things a bit better... resources are just for managing the successful start/close of a resource, not it's whole lifecycle, so the Resource.extension() is made for start/close per test/spec... the reason why I was trying to do this was to reuse resources already used in my service code... like my client or service classes based on it. Using the standard Kotest wiremock listener doesn't bridge between those two worlds.