47 Degrees joins forces with Xebia read more

Kotest Arrow extensions 1.3.0 is now available

Kotest Arrow extensions 1.3.0 is now available

Kotest Arrow extension libraries releases version 1.3.0, adding a new multiplatform arrow-fx-coroutines assertion module complementing kotest-assertions-arrow with combinators for Arrow Fx.

Thanks for all the feedback and contributions!

Table of Contents

Setting up Kotest Assertion for Arrow Fx

The Gradle setup is fairly straightforward:

dependencies {
  implementation("io.arrow-kt:arrow-fx-coroutines:arrow_version") // assuming this is not in the project classpath
  testImplementation("io.kotest.extensions:kotest-assertions-arrow-fx-coroutines:1.3.0")
}

It applies similarly in Maven:

<dependency>
    <groupId>io.kotest.extensions</groupId>
    <artifactId>kotest-assertions-arrow-fx-coroutines-jvm</artifactId>
    <version>1.3.0</version>
    <scope>test</scope>
</dependency>

Try out our various templates for a fast and easy set-up.

Resource combinators

Combinators for Resource simplify integration tests or testing various kinds of dependencies.

Including smart-casted assertions:

  • Resource#shouldBeResource
class ResourceSpec : StringSpec({
  "Int Resources are the same" {
    checkAll(Arb.int()) { n ->
      val b: Int = Resource.just(n).shouldBeResource(n)
      b shouldBe n
    }
  }
})

or comparing different Resource results like here:

"resource equality" {
  checkAll(Arb.int()) { n ->
    val a = Resource({ n }, { _, _ -> Unit })
    val b = Resource({ n }, { nn, _ -> println("release $nn") })

    a.shouldBeResource(b) shouldBe n
  }
}

Safely consume Resources

A key extension function to consume Resources safely - without Resource violations - in any Kotest [Spec] is Resource#extension.

See an example of Hikari and Exposed below.

fun hikari(config: HikariConfig): Resource<DataSource> =
  Resource.fromCloseable { HikariDataSource(config) }

fun database(ds: DataSource): Resource<Database> =
  Resource(
    acquire = { Database.connect(ds) },
    release = { db, _: ExitCase -> closeAndUnregister(db) }
  )

class DependencyGraph(val database: Database)

fun dependencies(config: HikariConfig): Resource<DependencyGraph> =
  resource {
    val ds = hikari(config).bind()
    val db = database(ds).bind()
    DependencyGraph(db)
  }

In a Kotest [Spec], we can safely consume the database with Kotest [MountableExtension] using [install]:

import io.kotest.core.extensions.install
import io.kotest.assertions.arrow.fx.coroutines.extension

class DatabaseSpec : StringSpec({
  val config = HikariConfig().apply {
    // add config settings
  }

  val dependencyGraph: DependencyGraph = install(dependencies(config).extension())

  // follow up with tests
  "test" {
    val database: Database = dependencyGraph.get().database
  }
})

There is an option to register [Resource] on a Project wide configuration with [ProjectResource] which interoperates with Kotest [Extension].

import io.kotest.core.config.AbstractProjectConfig
import io.kotest.assertions.arrow.fx.coroutines.ProjectResource

object ProjectConfig: AbstractProjectConfig() {
  val config = HikariConfig().apply {
    // add config settings
  }
  
  val dependencyGraph: ProjectResource<DependencyGraph> = 
    ProjectResource(dependencies(config))

  override fun extensions(): List<Extension> = listOf(dependencyGraph)
}

class MySpec : StringSpec({
  "test project wide database" {
    val database: Database = ProjectConfig.dependencyGraph.get().database
  }
})

ExitCase

The library contains smart-casted operators for ExitCase like ExitCase#shouldBeCompleted, among others:

import kotlinx.coroutines.CompletableDeferred
import io.kotest.assertions.arrow.fx.coroutines.resource
import io.kotest.assertions.arrow.fx.coroutines.shouldBeCompleted
import arrow.fx.coroutines.ExitCase
import arrow.core.identity

class ExitCaseSpec: StringSpec({
  "value resource is released with Completed" {
    checkAll(Arb.int()) { n: Int ->
      val completable = CompletableDeferred<ExitCase>()
      val nn: Int = Resource({ n }, { _, ex -> completable.complete(ex) }).use(::identity)

      nn shouldBe n
      completable.await().shouldBeCompleted()
    }
  }

  "shouldBeCancelled(e)" {
    checkAll(Arb.string().map { CancellationException(it) }) { e ->
      ExitCase.Cancelled(e).shouldBeCancelled(e)
    }
  }
})

There will be a follow-up blog post with more in-depth content on using Kotest Arrow extension libraries.

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.