skip to content

Compare FunSpec and DescribeSpec for nesting tests. How do `test`/`context` and `describe`/`it` work, and when would you choose one over the other?

level: middleimportance: should knowfreq 50%

answer

  1. FunSpec: test() leaf, context() group
  2. DescribeSpec: describe() group, it() leaf
  3. context() exists in both as a synonym container
  4. Only leaf blocks run; containers just name/nest
  5. Choice = readability/team origin, not power

basics

~10 s

FunSpec uses test("...") for cases and context("...") to group them. DescribeSpec uses describe("...") to group and it("...") for cases, like Jest/RSpec. Both nest; pick by team style.

solid answer

~40 s

In **FunSpec**, a single test is `test("name") { }`; you group related tests with `context("name") { }`, which can nest. In **DescribeSpec**, you group with `describe("name") { }` (nestable) and declare cases with `it("name") { }`; `context("name")` is also available as a synonym group. The key difference is vocabulary and reader expectation: FunSpec reads as plain function-style tests, DescribeSpec mirrors RSpec/Jest "describe … it should …" which suits behavior specs and teams coming from JS/Ruby. Both support arbitrary nesting, the same lifecycle (e.g. `beforeTest`, `beforeContainer`), and the same matchers. Choose FunSpec for concise unit tests, DescribeSpec when you want descriptive, deeply nested specification-style names.

go deeper

for a junior

Knows test()/context() for FunSpec and describe()/it() for DescribeSpec at a basic level.

for a middle

Correctly nests both, knows containers vs leaves, and articulates the readability-driven choice.

for a senior

Maps DescribeSpec to RSpec/Jest migration ergonomics and picks styles per test category deliberately.

for a principal

Sets project conventions for which style fits unit vs spec tests and ensures consistency across teams.

## FunSpec FunSpec is the function-call style. A leaf test is declared with `test("...") { }`. To group, you use `context("...") { }`, which can be nested arbitrarily. ```kotlin import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe class CalculatorTest : FunSpec({ context("addition") { test("adds positives") { (2 + 3) shouldBe 5 } context("with negatives") { test("adds a negative") { (2 + -3) shouldBe -1 } } } }) ``` ## DescribeSpec DescribeSpec mirrors RSpec (Ruby) and Jest/Mocha (JS). Containers are `describe("...") { }`; leaf tests are `it("...") { }`. `context("...")` also exists as an alternative container word. ```kotlin import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe class CalculatorDescribe : DescribeSpec({ describe("addition") { it("adds positives") { (2 + 3) shouldBe 5 } context("with negatives") { it("adds a negative") { (2 + -3) shouldBe -1 } } } }) ``` ## Containers vs leaves In both, only the leaf (`test` / `it`) blocks are runnable cases; `context`/`describe` are containers that organize and name them. Container names are concatenated into the reported test path. ## Choosing between them - **FunSpec** — minimal, function-like; great default for unit tests. - **DescribeSpec** — descriptive "describe X / it does Y"; natural for teams from Jest/RSpec or for specification-style tests. Both share identical lifecycle callbacks and matcher support, so the choice is about readability and team familiarity, not capability. They can coexist across different test classes in one project.

  • Can you nest containers in FunSpec?
    Yes — context blocks nest arbitrarily, and you place test leaves at any depth.
  • Is `context` available in DescribeSpec?
    Yes, DescribeSpec supports context as an alternative container alongside describe; it leaves are the runnable cases.

saying these in an interview costs you the question

  • Saying describe/it blocks all run as separate tests (only it leaves do)
  • Claiming FunSpec cannot nest
  • Confusing it() with test() across the wrong style
  • Asserting one style has more matcher power than the other

context