Skip to main content

Quickstart — Kotlin & JUnit 5

This guide walks through setting up Kensa in a Kotlin project with JUnit 5 and writing your first test.

1. Apply the Kensa Gradle Plugin

The Kensa Gradle plugin wires the Kotlin compiler plugin (so @RenderedValue and @ExpandableSentence capture values) and pulls the right kensa-core variant onto your compilation. Apply it alongside Kotlin:

build.gradle.kts
plugins {
kotlin("jvm") version "2.4.0" // the plugin enforces a minimum Kotlin version — see the compatibility matrix
id("dev.kensa.gradle-plugin") version "<plugin-version>"
}

repositories { mavenCentral() }

The plugin and kensa-core version independently — see the compatibility matrix for the supported pairings.

2. Add Test Dependencies

Pick a JUnit variant and one or more assertions bridges. The kensa-bom lines up versions across the framework and assertions artifacts so you don't have to repeat them:

build.gradle.kts
dependencies {
testImplementation(platform("dev.kensa:kensa-bom:<kensa-core-version>"))
testImplementation("dev.kensa:kensa-framework-junit5") // for JUnit 5; use kensa-framework-junit6 for JUnit 6

// Pick one assertions bridge (or use multiple)
testImplementation("dev.kensa:kensa-assertions-kotest") // Kotest matchers
testImplementation("dev.kensa:kensa-assertions-hamkrest") // HamKrest
}

The Gradle plugin adds kensa-core (with the core-hooks capability the compiler plugin needs) onto your compilation — you don't need to declare it here. Find the latest kensa-core version on GitHub releases.

Migrating from 0.6.x

If you were previously adding -Xskip-prerelease-check to your Kotlin compile task for Kensa, you can remove it — 0.7.0 no longer requires it. Its inclusion in earlier versions was a mistake.

3. Write a Test

Implement KensaTest and mix in an assertions bridge. No @ExtendWith is needed — the KensaExtension is pulled in automatically via the interface, and the lifecycle listener is registered via the JUnit Platform ServiceLoader. Test methods follow the Given–When–Then structure using the given(), whenever(), and then() DSL.

import dev.kensa.Action
import dev.kensa.ActionContext
import dev.kensa.GivensContext
import dev.kensa.RenderedValue
import dev.kensa.StateCollector
import dev.kensa.junit.KensaTest
import dev.kensa.kotest.WithKotest
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test

class LoanDecisionTest : KensaTest, WithKotest {

@RenderedValue
private val applicantName = "Alice"

@RenderedValue
private val requestedAmount = 10_000

private val service = LoanService()
private lateinit var applicant: Applicant
private lateinit var result: LoanResult

@Test
fun canApproveLoanForApplicantWithGoodCredit() {
given(anApplicantWithGoodCredit())
whenever(theLoanServiceProcessesTheApplication())
then(theLoanResult()) { status shouldBe LoanStatus.Approved }
}

@Test
fun canDeclineLoanForApplicantWithPoorCredit() {
given(anApplicantWithPoorCredit())
whenever(theLoanServiceProcessesTheApplication())
then(theLoanResult()) { status shouldBe LoanStatus.Declined }
}

// --- Givens ---

private fun anApplicantWithGoodCredit() = Action<GivensContext> {
applicant = Applicant(applicantName, creditScore = 750, amount = requestedAmount)
}

private fun anApplicantWithPoorCredit() = Action<GivensContext> {
applicant = Applicant(applicantName, creditScore = 300, amount = requestedAmount)
}

// --- Action ---

private fun theLoanServiceProcessesTheApplication() = Action<ActionContext> {
result = service.process(applicant)
}

// --- State ---

private fun theLoanResult() = StateCollector { result }
}

What's happening here

ElementPurpose
KensaTestProvides the Given–When–Then DSL; registers the JUnit extension
WithKotestAdds then() / and() overloads that accept Kotest matchers
@RenderedValueField value is captured and shown in the HTML report
Action<GivensContext>Lambda that runs during given() — sets up test state
Action<ActionContext>Lambda that runs during whenever() — exercises the system
StateCollector<T>Lambda that returns a value for then() to assert against

For shared, lazily-created test data — reusable across tests and rendered in the report's Fixtures tab — see Fixtures.

4. Chain Multiple Steps

Use and() to chain additional setup or assertions:

@Test
fun canApproveLoanWithUnderwritingApproval() {
given(anApplicantWithGoodCredit())
and(anApprovalFromUnderwriting())

whenever(theLoanServiceProcessesTheApplication())

then(theLoanResult()) { status shouldBe LoanStatus.Approved }
and(theLoanReference()) { shouldStartWith("LN-") }
}

5. Record Interactions (Sequence Diagrams)

Capture calls between components in your whenever() action using interactions.capture(). Kensa renders these as a sequence diagram in the HTML report. Participants are Party implementations — an enum is the usual choice:

import dev.kensa.state.CapturedInteractionBuilder.Companion.from
import dev.kensa.state.Party

enum class LoanParties : Party {
Client, LoanProcessor;

override fun asString(): String = name
}
private fun theLoanServiceProcessesTheApplication() = Action<ActionContext> { ctx ->
ctx.interactions.capture(
from(LoanParties.Client).to(LoanParties.LoanProcessor).with("process(${applicant.name})", "Loan Request")
)

result = service.process(applicant)

ctx.interactions.capture(
from(LoanParties.LoanProcessor).to(LoanParties.Client).with(result, "Loan Result")
)
}

6. Run & View the Report

Run your tests normally with Gradle:

./gradlew test

By default, reports are written to a kensa-output directory in the system temp folder. Configure a fixed location in your test setup:

Kensa.konfigure {
outputDir = Path("build/kensa-output")
}

Then open index.html in a browser, or use the Kensa CLI to serve them:

kensa --dir build/kensa-output

Other Frameworks

Kensa also supports Kotest and TestNG. The Given-When-Then DSL is identical — only the dependency and setup differs. See the Kotest Quickstart or the TestNG Quickstart, or the Java Quickstart for Java-specific setup. Working examples are linked from each quickstart.