skip to content

Assertion & Extension Modules

Kotest is deliberately modular: assertions, property testing and the runner ship as separate artifacts, with integrations in their own family. Interviewers ask for the map to see if you pick modules.

on this pageshow

explore

questions

3

Kotest is published as several separate artifacts rather than one jar. Name the main ones and say what each provides — matchers, property testing, data-driven testing, JSON assertions, and the test runner.

level: juniorimportance: should knowfreq 32%

answer

  1. runner-junit5 = executes specs
  2. assertions-core = the matcher DSL
  3. property = Arb / Exhaustive / checkAll
  4. framework-datatest = withData
  5. extensions live under io.kotest.extensions

basics

~10 s

kotest-runner-junit5 runs specs; kotest-assertions-core holds the shouldBe/shouldContain matcher family; kotest-property provides Arb/Exhaustive property testing; kotest-framework-datatest provides withData; kotest-assertions-json adds JSON matchers. You add only the modules you use.

solid answer

~50 s

Kotest is deliberately modular, so a project takes only what it needs: - **kotest-runner-junit5** — the runner that discovers and executes Kotest specs; it is what makes specs run at all. - **kotest-assertions-core** — the core matcher DSL (`shouldBe`, `shouldContain`, collection/string/throwable matchers, `assertSoftly`, clues). - **kotest-property** — property-based testing: `Arb`, `Exhaustive`, `checkAll`/`forAll`. - **kotest-framework-datatest** — data-driven testing (`withData`). - **kotest-assertions-json** — JSON-specific matchers, a separate module so projects that do not test JSON do not pull it in. Beyond these, additional assertion and integration modules live in Kotest's separate *extensions* project under the `io.kotest.extensions` group — Ktor assertions and the Spring, Testcontainers, WireMock and Koin extensions among them. The important consequence is that the assertion modules do not depend on the runner: `kotest-assertions-core` can be used from a non-Kotest test, and conversely you can run Kotest specs and assert with something else.

go deeper

for a junior

Name the runner, the assertions module, and know that property testing and data-driven testing are separate artifacts.

for a middle

Add that assertions are runner-independent, that JSON assertions are their own module, and that the integrations live under a separate group.

for a senior

Explain the adoption consequences — matchers first, specs later — and the version-alignment concern between core artifacts and the independently released extensions.

for a principal

Treat the module map as a dependency-surface decision: keep the test classpath minimal, align core versions centrally, and adopt an extension only when it manages a real lifecycle.

## Why Kotest is split up Kotest is not a single jar. It is a family of artifacts under the `io.kotest` group, plus a separate ecosystem under `io.kotest.extensions`. The split exists because the pieces are genuinely independent: the assertion DSL, the property-testing engine, the data-driven helpers and the runner are useful on their own, and most projects want only some of them. Modularity also keeps the dependency footprint of a test suite small and lets each piece evolve at its own pace. ## The core artifacts **kotest-runner-junit5** — the execution side. It provides the machinery that discovers spec classes and runs the tests they register. Without it, spec classes exist but nothing executes them. This is the artifact whose name people remember because it is the one that must be present for a Kotest suite to run at all. **kotest-assertions-core** — the matcher library: `shouldBe` / `shouldNotBe`, the collection matchers, string matchers, throwable matchers, `assertSoftly`, and the clue functions. This is by far the most-used module, and it is *runner-independent* — its matchers throw ordinary assertion errors, so they work inside any test that a JVM test framework can run. Teams frequently adopt Kotest assertions long before (or without) adopting Kotest specs. **kotest-property** — the property-based testing engine: the `Arb` (random, shrinking) and `Exhaustive` (enumerated) generator types and the `checkAll` / `forAll` entry points. Also usable from non-Kotest tests. **kotest-framework-datatest** — the data-driven testing support, notably `withData`, which registers one test per data element with a derived name. It is a separate module because it hooks into the framework's test-registration machinery. **kotest-assertions-json** — matchers for JSON documents. Kept separate so projects that never touch JSON do not pull in the extra dependency. Kotest also publishes a **BOM** artifact (`kotest-bom`) so the versions of the core modules can be aligned in one place instead of repeated per dependency. ## The extensions ecosystem Integration modules live in Kotest's separate extensions project, published under the `io.kotest.extensions` group. That is where you find, among others: - **kotest-extensions-spring** — provides `SpringExtension`, which lets specs participate in Spring's test context (the mechanics of Spring test support belong to Spring's own topic, not here); - **kotest-extensions-testcontainers** — attaches Testcontainers containers to the Kotest lifecycle; - **kotest-extensions-wiremock** — attaches a WireMock server to the lifecycle; - **kotest-extensions-koin** — Koin integration; - **kotest-assertions-ktor** — Ktor-specific assertions; - **kotest-assertions-arrow** — matchers for Arrow types. Because these are a separate project, they version independently of the core artifacts; you cannot assume a core version number is also a valid extension version number. Check the coordinates and versions for the specific extension you need rather than copying a core version across. ## What this modularity buys you 1. **Mix and match.** Use Kotest's matchers with another runner, or Kotest specs with another assertion library. Neither side requires the other. 2. **Incremental adoption.** A team can introduce `kotest-assertions-core` into an existing suite as a pure readability improvement, with no change to how tests are discovered or run, and adopt specs later. 3. **Smaller test classpaths.** JSON matchers, property testing, and every integration extension are opt-in. 4. **Clear ownership when something is missing.** "`withData` is unresolved" is a missing datatest module, not a spec-style mistake; "`Arb` is unresolved" is a missing property module. Knowing the map turns a confusing compile error into a one-line fix. ## Common confusions - Assuming one dependency gives you everything — property testing and data-driven testing in particular are separate modules. - Assuming the integration extensions share the core version number; they are a separate project with its own release train. - Thinking the assertion library requires the Kotest runner. It does not. - Reaching for an extension when a few lines of setup would do. Extensions earn their keep when they manage a lifecycle (start/stop a container, reset a mock server between tests); for a one-off, plain code is often simpler. ## Interview framing This is a setup-literacy question. Interviewers ask it to check that a candidate has actually configured a Kotest project rather than inherited one — the tell is whether they know that matchers, property testing and data-driven testing are *separate* modules, and that the integration extensions live in their own group.

  • Can you use Kotest's matchers without using Kotest to run your tests?
    Yes — the assertion modules are independent of the runner. Matchers signal failure with ordinary assertion errors, so they work in any test a JVM test framework can execute. This is the usual incremental-adoption path: bring in the matcher module for readability first, and decide about spec styles separately.
  • A colleague's `withData(...)` call does not resolve even though Kotest runs fine. What is your first guess?
    The data-driven module is missing — `withData` lives in Kotest's separate datatest artifact, not in the core runner or assertions. The same class of mistake produces unresolved `Arb`/`checkAll` when the property module is absent. Because Kotest is deliberately modular, an unresolved API is usually a missing artifact rather than a wrong import path.

saying these in an interview costs you the question

  • Believing a single Kotest dependency provides matchers, property testing and data-driven testing together
  • Thinking Kotest's matcher library requires Kotest's own runner
  • Assuming the integration extensions share the core artifacts' version number
  • Inventing artifact names instead of checking the coordinates for the extension you need

context

open as a page

Kotest ships integration modules such as its Spring, Testcontainers and WireMock extensions. At a mechanical level, how does an extension from one of those artifacts get attached to a spec or to a whole project?

level: middleimportance: should knowfreq 30%

basics

~20 s

You register the extension object: per spec via the spec's extension()/extensions() declaration, or project-wide by listing it in your AbstractProjectConfig. Extensions that own a resource are mounted with install(), which starts it on the spec lifecycle and returns the materialized value.

open as a page

Kotest's core artifacts and its integration modules for Spring, Testcontainers, WireMock and Koin are published as two different families. Why does that split matter in practice when you maintain a real test suite?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

The integration modules live in a separate project under the io.kotest.extensions group with its own release train, so their versions are not the core version. You align core artifacts centrally, pin extension versions explicitly, and expect them to lag or lead core releases.

open as a page