In a Kotest 5 spec, what does defaultTestConfig do, and how does it differ from calling .config(...) on an individual test?
answer
- TestCaseConfig: enabled, invocations, timeout, tags, extensions, severity
- defaultTestConfig = spec-wide baseline, includes nested tests
- \.config(...) = one test, wins over the spec baseline
- unset field inherits outward, not framework default
- repeated .config in a spec → hoist it
basics
~10 sdefaultTestConfig supplies a TestCaseConfig used as the baseline for every test in that spec, overriding project-wide defaults. Calling .config(...) on one test overrides that baseline again for that test only. Same settings, different scope.
solid answer
~50 sBoth carry a `TestCaseConfig` — the per-test settings bundle: `enabled`/`enabledIf`, `invocations`, `timeout`, `invocationTimeout`, `tags`, `extensions`, `severity`. ```kotlin class IntegrationTests : FunSpec() { override fun defaultTestConfig() = TestCaseConfig( timeout = 2.minutes, tags = setOf(Integration) ) init { test("normal") { /* 2 minutes, tagged Integration */ } test("the slow one").config(timeout = 10.minutes) { /* overrides */ } } } ``` `defaultTestConfig` applies to **every** test in the spec, including nested ones, and sits between the project default and per-test config in the precedence chain. `.config(...)` is the narrowest scope and wins. Use `defaultTestConfig` when a property is true of the whole spec — all its tests are integration tests, all need a longer timeout. Use `.config(...)` for the single exception. Repeating the same `.config(...)` on every test in a spec is the signal to hoist it into `defaultTestConfig`.
code
kotlin · 11 linesclass IntegrationTests : FunSpec() {
override fun defaultTestConfig() = TestCaseConfig(
timeout = 2.minutes,
tags = setOf(Integration)
)
init {
test("reads from the database") { /* 2 min, tagged */ }
test("the enormous import").config(timeout = 10.minutes) { /* still tagged */ }
}
}go deeper
Know that a spec can set defaults for all its tests and that an individual test can override them.
Name the TestCaseConfig fields, state that the baseline reaches nested tests, and explain field-by-field resolution.
Use the hoisting heuristic to keep rules outward and exceptions visible, and diagnose surprises through the precedence chain.
Turn it into convention: which facts belong to a spec's baseline, which to project config, and how to keep configuration readable as specs multiply across teams.
## One settings bundle, three scopes Kotest expresses per-test settings as a `TestCaseConfig`. The same bundle appears at three scopes: - project — global defaults in `AbstractProjectConfig` (individual properties such as `timeout`), - spec — `defaultTestConfig` for every test in the spec, - test — `.config(...)` on a single test. Resolution is most-specific-wins, so the mental model is a three-layer default chain, not three independent switches. ## What is in a TestCaseConfig - `enabled` / `enabledIf` — whether the test runs at all (a static flag, or a predicate evaluated for the test case). - `invocations` — run the test body more than once. - `timeout` — limit for the whole test. - `invocationTimeout` — limit for a single invocation when `invocations` > 1. - `tags` — Kotest tags used for filtering. - `extensions` — `TestCaseExtension`s attached at this scope. - `severity` — the severity level attached to the test. Anything you leave unset falls through to the next layer out. ## Spec scope: defaultTestConfig ```kotlin class IntegrationTests : FunSpec() { override fun defaultTestConfig() = TestCaseConfig( timeout = 2.minutes, tags = setOf(Integration) ) init { test("reads from the database") { /* ... */ } context("when the queue is empty") { test("returns nothing") { /* also 2 minutes, also tagged */ } } } } ``` Two things matter here. First, it reaches **nested** tests, not just top-level ones — the baseline is for the spec, not for a scope. Second, it is a genuine baseline: a test that sets only `timeout` in its own `.config(...)` still inherits the spec's tags. This is the natural home for facts about the whole spec. "Every test in this file talks to a real database" should be stated once as a tag and a timeout, not repeated on twenty tests where the twenty-first will be forgotten. ## Test scope: .config(...) ```kotlin test("the enormous import").config(timeout = 10.minutes, invocations = 1) { /* ... */ } ``` The narrowest scope, and the most visible: the setting sits on the line a reader is already looking at. That visibility is why exceptions belong here and rules belong at spec or project level — a reader who sees a ten-minute timeout on one test knows immediately that this test is special. ## Choosing between them | Situation | Where | |---|---| | Every test in this spec is slow / tagged / needs an extension | `defaultTestConfig` | | One test is the exception | `.config(...)` | | Every spec in the project should behave this way | project config | A useful smell test: if you find yourself copying the same `.config(...)` onto each test in a spec, hoist it. If you find yourself overriding the same project default in most specs, the project default is wrong. ## Common mistakes - **Expecting it to override a per-test setting.** It cannot; the test wins. - **Expecting it to apply outside the spec.** It is spec-scoped; a sibling spec is unaffected. - **Confusing it with isolation mode.** Isolation is a property of how the *spec* is instantiated, not of a test case, and has its own spec-level property. - **Assuming an unset field means 'framework default'.** Unset means "inherit from the next layer out", which may be a project value you forgot about. - **Using tags here and expecting them to change what runs without a filter.** Tags only affect selection when a tag expression is active. ## Interview signal A good answer states the layering rather than describing two unrelated APIs, names a couple of real `TestCaseConfig` fields, and offers the hoisting heuristic — rules go outward, exceptions stay local and visible.
- A spec's defaultTestConfig sets tags and a timeout; one test overrides only the timeout with .config(...). Does that test keep the tags?Yes. Settings resolve field by field, not bundle by bundle: the test's config supplies a timeout, and every field it leaves unset falls through to the spec baseline and then to the project defaults. That is what makes defaultTestConfig usable as a genuine baseline rather than an all-or-nothing bundle.
- When would you push a setting from defaultTestConfig up to project config instead?When it is true of the whole suite rather than one spec — a global safety-net timeout, or an extension every spec needs. The counter-pressure is discoverability: a project setting is invisible from the spec it affects, so it should be limited to things that are uniform and uncontroversial. If most specs would override it, it does not belong globally.
saying these in an interview costs you the question
- Thinking defaultTestConfig overrides a per-test .config(...)
- Believing an unset field falls back to the framework default rather than the enclosing layer
- Expecting defaultTestConfig to affect other specs
- Conflating it with isolation mode, which is a spec-instantiation setting
- Assuming tags set here change which tests run even with no tag filter active