skip to content

In Kotest's BehaviorSpec, which blocks are containers and which are the actual leaf tests, and what is an `and` block for?

level: middleimportance: should knowfreq 35%

answer

  1. given/when = containers, then = leaf
  2. and = extra container in given/when scope
  3. backticked when vs capitalized Given/When/Then/And
  4. x-prefix disables any block
  5. names rendered with Given:/When:/Then: prefixes

basics

~20 s

given and when are containers: their bodies only register children. then blocks are the leaf tests that hold assertions and get counted in reports. An and block adds one more grouping level inside a given or a when.

solid answer

~50 s

A Kotest BehaviorSpec builds a tree of two node kinds. given(...) and when(...) are containers: running one executes its lambda, which registers the nodes nested inside. then(...) is a leaf test, the node that carries assertions and that the engine counts, reports and filters. and(...) is an extra container usable inside a given or a when scope, so a scenario with several preconditions or outcomes can be grouped without inventing another given. Because when is a Kotlin soft keyword you either backtick it or use the capitalized aliases Kotest ships (Given, When, Then, And), which behave identically. Each block has an x-prefixed disabled twin: xgiven, xwhen, xthen, xand. Displayed names are derived from the block strings with the keyword prefixed, so a leaf reads Given: ... When: ... Then: .... Operational rule: arrange and act in containers, assert in then.

code

kotlin · 12 lines
kotlin
class StackSpec : BehaviorSpec({
    Given("a stack with one element") {
        val stack = ArrayDeque(listOf(1))
        When("the element is popped") {
            val popped = stack.removeLast()
            And("nothing else is pushed") {
                Then("the popped value is returned") { popped shouldBe 1 }
                Then("the stack is empty") { stack.shouldBeEmpty() }
            }
        }
    }
})

go deeper

for a junior

Recall the vocabulary: given/when group, then is the test, and adds grouping, x disables.

for a middle

Explain container versus leaf, that container bodies execute and register children, and where setup versus assertions belong.

for a senior

Add the consequences: a throwing container hides its unregistered children, containers re-run per execution, leaves are the unit of reporting and filtering.

for a principal

Frame it as a naming and reporting contract: the derived name path is the identity CI dashboards track, so block wording and nesting depth are review-worthy decisions.

## Two kinds of node Kotest turns every spec into a tree of test cases. A node is either a container (it may have children) or a leaf (it has none and its body is the test). That tree is what the engine executes and what the IDE and build reports render. BehaviorSpec fixes a BDD vocabulary onto that tree: - given("...") - container, outermost level, the starting state. - when("...") - container nested in a given, the action under test. - then("...") - leaf test, the expected outcome and where matchers go. - and("...") - an additional container available inside given and when scopes. ## What container execution actually does A container body is ordinary Kotlin that runs when the container runs. Every nested builder call it makes registers a child node. So the strings are not annotations scanned up front; the tree is built by executing code. Two consequences follow. First, anything you write in a given or when body runs before its children exist, which makes containers the natural home for fixtures and for the action being exercised. Second, if a container body throws, the children after the throw are never registered, so they cannot be reported as failures - they simply do not appear. ## Why and blocks exist BDD prose often needs more than three clauses: given a stack, and it has one element, when I pop, and pop again, then it is empty. Rather than force you to nest a second given, BehaviorSpec offers and as a plain container in both the given and the when scopes. It changes the name path and the grouping only; then remains the only leaf. ## The when keyword problem and the capitalized aliases when is a soft keyword in Kotlin, so the lowercase form must be backtick-escaped at the call site. Kotest therefore also exposes Given, When, Then and And with identical behaviour. Picking one casing per codebase avoids files that mix backticked and capitalized forms. ## Derived names Kotest prefixes the displayed name of each block with its keyword, so the string you pass should not repeat it: write given("a stack with one element") rather than given("Given a stack..."), or reports read Given: Given a stack. The full path of prefixed names is the test's identity for reporting and for name-based filtering such as Kotest's kotest.filter.tests system property. ## Placement rules that matter in review Assertions belong in then because the leaf is the unit that is named, counted, retried and filtered. Expensive shared setup belongs as high in the tree as it is still correct, because a container body re-runs for each execution of that container. Mutating shared state inside a then can leak into sibling leaves unless the spec's isolation configuration creates a fresh instance, so prefer building state in the container and only reading it in the leaf.

  • Can a then block contain another then?
    No. then registers a leaf test, which by definition has no children, so there is no scope inside it that offers further test builders. If you need another level of grouping, add an and container above the then blocks, or introduce another when.
  • Why do the capitalized aliases exist at all?
    when is a Kotlin soft keyword, so calling a function named when requires backticks. Kotest added Given/When/Then/And so BDD specs can be written without escaping. They register exactly the same nodes; the choice is purely stylistic and worth standardizing per codebase.

saying these in an interview costs you the question

  • Saying given/when are just labels whose bodies are never executed
  • Putting assertions in a when block and treating then as decoration
  • Believing and creates a leaf test
  • Repeating the keyword in the string, producing 'Given: Given a stack'
  • Assuming backticked when and capitalized When behave differently

context