Your team is standardizing Kotest spec styles across a large codebase. What trade-offs and pitfalls (test naming, focus/bang prefixes, nesting depth) guide picking and enforcing a style?
answer
- Style is per-class, readability-only — engine identical
- Map FunSpec→unit, BehaviorSpec/DescribeSpec→BDD
- Container names concatenate → cap nesting depth
- f: focus and ! bang prefixes — gate them in CI
- Enforce a small approved style set via lint/review
basics
~20 sPick one or two styles, match them to test types (unit vs BDD), and standardize naming. Watch for deep nesting hurting readability and for prefix features like focusing or disabling tests behaving consistently across styles.
solid answer
~40 sStandardizing means choosing styles that fit your test categories — e.g. **FunSpec** for unit tests (concise), **BehaviorSpec/DescribeSpec** for acceptance/spec-by-example. Key governance points: (1) **Naming** — container names concatenate into the reported path, so a consistent convention keeps reports legible; over-deep nesting (especially DescribeSpec/BehaviorSpec) produces long, noisy names. (2) **Focus and bang prefixes** — Kotest lets you prefix a top-level test name with `f:` to focus (run only it) or `!` (bang) to disable it; these work on string-named tests and you must ensure CI fails if a focused/disabled test is committed. (3) **Consistency** — since style is per-class, enforce via lint/review so the codebase doesn't sprawl across all five styles. (4) Mixing is allowed but should be deliberate. The engine, matchers, and lifecycle are identical, so the decision is about readability and maintainability.
go deeper
Recognizes the styles exist and that one should be picked consistently.
Maps styles to test types and knows nesting affects report names.
Calls out focus/bang prefixes, naming conventions, and SingleInstance migration risks.
Defines and enforces a style policy (lint/review/CI gating) across a large codebase, balancing readability, reporting, and safety.
## Framing the decision Spec style is a **per-class** choice and purely about layout/readability — engine, matchers, lifecycle, and isolation are identical across styles. So standardization is a maintainability and reporting concern, not a capability one. ## Matching styles to test types - **FunSpec** — concise function-style; a strong default for unit tests. - **DescribeSpec** — RSpec/Jest familiarity; good for descriptive specification tests and teams migrating from JS/Ruby. - **BehaviorSpec** — given/when/then BDD; fits acceptance/spec-by-example where business-readable names matter. - **StringSpec** — ultra-minimal; great for tiny, flat test sets but cannot nest. ## Pitfall 1 — test naming and report paths Container names (`context`, `describe`, `given`/`when`) are concatenated into the reported test name. Deeply nested BehaviorSpec/DescribeSpec yields long names like "Given… When… Then…". Set a convention (sentence-style, present tense) and cap nesting depth to keep CI reports and failure messages readable. ## Pitfall 2 — focus and bang prefixes Kotest supports **test-name prefixes** on top-level tests: - `f:` (focus) — only focused tests in that spec run; everything else is skipped. - `!` (bang) — disables/ignores that test. ```kotlin import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe class FocusExample : StringSpec({ "f:only this runs" { (1 + 1) shouldBe 2 } "this is skipped while a focus exists" { (2 + 2) shouldBe 4 } "!disabled test" { error("never runs") } }) ``` Governance: a committed `f:` silently skips sibling tests and a `!` silently disables coverage — add a CI/lint check (or Kotest config) to fail the build if focus/bang prefixes are present. ## Pitfall 3 — sprawl Because each class can pick its own style, an ungoverned codebase ends up using all five. Enforce a small approved set via code review or a custom lint/detekt rule. ## Pitfall 4 — migration When migrating (e.g. JUnit → Kotest, or StringSpec → FunSpec), remember tests register at construction time and default isolation is SingleInstance — naive copy of per-method state assumptions can introduce shared-state bugs. ## Bottom line Pick a minimal, intentional style palette mapped to test categories; standardize naming/nesting; and gate focus/bang prefixes in CI so they never silently shrink the suite.
- Why is a committed `f:` (focus) prefix dangerous in CI?Focusing makes only that test run in its spec, silently skipping its siblings, so coverage drops without an obvious failure unless you gate it.
- Does standardizing on one style limit your matchers or lifecycle hooks?No — all styles share the same matchers, lifecycle callbacks, and isolation modes; standardization is purely about layout and readability.
- Which style cannot nest, and when is that fine?StringSpec is flat with no nesting; it's fine for small, simple test sets where grouping adds no value.
saying these in an interview costs you the question
- Treating style choice as a capability difference rather than readability
- Ignoring that focus/bang prefixes can silently shrink the suite
- Allowing unbounded nesting that produces unreadable report names
- Letting every class pick a different style with no governance
- Migrating without accounting for SingleInstance shared state