Which test settings can you set globally in Kotest's AbstractProjectConfig, and what wins when a spec or an individual test declares the same setting?
answer
- most specific wins: test > spec > project > framework
- isolationMode, timeout, assertionMode, testCaseOrder
- failOnEmptyTestSuite, duplicateTestNameMode
- extensions() + beforeProject/afterProject
- a default everyone overrides is the wrong default
basics
~10 sProject config holds run-wide defaults: isolationMode, timeout and invocationTimeout, assertionMode, testCaseOrder, concurrency settings, duplicateTestNameMode, failOnEmptyTestSuite, and global extensions. Resolution is most-specific-wins: per-test .config() beats spec-level defaultTestConfig, which beats the project default.
solid answer
~50 s`AbstractProjectConfig` is the run-wide default layer. Commonly used overrides: - **Execution model**: `isolationMode`, `testCaseOrder`, `parallelism` / concurrency settings. - **Time limits**: `timeout` (per test), `invocationTimeout` (per invocation), `projectTimeout`. - **Strictness**: `assertionMode`, `duplicateTestNameMode`, `failOnEmptyTestSuite`. - **Wiring**: `extensions()`, plus `beforeProject`/`afterProject`. Resolution is **most specific wins**: 1. per-test `.config(timeout = …)` 2. spec-level `defaultTestConfig` (and spec-level properties such as `isolationMode`) 3. project config 4. Kotest's built-in default So a project timeout of 30s is a safety net, not a ceiling: any spec or test may raise or lower it. Several settings can additionally be supplied through system properties or a `kotest.properties` file on the classpath, which is how CI adjusts a run without a code change. The practical consequence: put in project config only what should be uniform, and expect the exceptions to be declared locally where a reader can see them.
code
kotlin · 15 linespackage io.kotest.provided
class ProjectConfig : AbstractProjectConfig() {
override val isolationMode = IsolationMode.SingleInstance
override val timeout = 30.seconds
override val failOnEmptyTestSuite = true
override fun extensions() = listOf(MetricsListener)
}
class SlowIntegrationTests : FunSpec() {
override fun defaultTestConfig() = TestCaseConfig(timeout = 2.minutes)
init {
test("imports a large file") { /* ... */ }
}
}go deeper
Name a few settings that live in project config and know that per-test settings win over global ones.
State the full precedence chain and give real examples of settings at each layer.
Use precedence to diagnose 'setting ignored' reports and argue which defaults belong global versus local.
Own the policy: globals for invariants that must be uniform and fail loudly, locals for exceptions that carry information, plus a rollout plan for changing a global in a large suite.
## The layer cake Kotest resolves each setting by asking the most specific source first and falling back outwards: ``` per-test .config(...) → spec (defaultTestConfig / spec properties) → project config → framework default ``` This is worth stating explicitly in an interview, because it explains almost every "my setting is ignored" report. A global default is exactly that — a default. It never overrides a local declaration. ## What lives at project level **Execution model** - `isolationMode` — the default spec instantiation strategy for every spec that does not declare its own. - `testCaseOrder` — the order in which tests within a spec are run. - concurrency settings (`parallelism`, and the concurrent-spec/concurrent-test knobs) — how much runs at once. **Time limits** - `timeout` — default limit for a single test. - `invocationTimeout` — limit for one invocation when a test is configured to run several times. - `projectTimeout` — an upper bound on the whole run, which is what stops a hung suite from occupying CI for an hour. **Strictness / hygiene** - `assertionMode` — whether a test that executed no Kotest assertion is a warning, an error, or ignored. - `duplicateTestNameMode` — what happens when two tests in a scope share a name. - `failOnEmptyTestSuite` — turns "nothing ran" from a green build into a failure, which catches broken filters and misconfigured includes. **Wiring** - `extensions()` — listeners and extensions applied to every spec. - `beforeProject()` / `afterProject()` — run-wide setup and teardown. You override only what you need; every untouched property keeps the framework default. ## How a spec overrides A spec can declare `defaultTestConfig` to change the defaults for all of its tests: ```kotlin class SlowIntegrationTests : FunSpec() { override fun defaultTestConfig() = TestCaseConfig(timeout = 2.minutes) init { test("imports a large file") { /* ... */ } } } ``` and an individual test overrides again: ```kotlin test("the really slow one").config(timeout = 10.minutes) { /* ... */ } ``` Some settings — isolation mode is the obvious one — are spec-shaped rather than test-shaped and have their own spec-level property. ## Choosing good globals A global default earns its place when uniformity is the point: - `failOnEmptyTestSuite = true` — nobody wants a green build from zero tests. - `duplicateTestNameMode` set to error — duplicate names silently hide tests in some styles and break reporting in others. - a generous `timeout` — a safety net so a hung test fails in a minute rather than blocking CI. - `assertionMode` — a whole-suite quality bar. - `extensions()` — cross-cutting infrastructure that must apply everywhere. A global default is a *bad* default when most specs override it. If half the suite raises the timeout, the global value is not expressing a shared decision; it is generating noise. Either raise it or accept per-spec declarations as the norm and drop the global. ## The discoverability cost The weakness of project config is invisibility. A developer reading a failing spec has no local hint that a global isolation mode, a global timeout, or a globally registered extension is in play. Two mitigations: keep the config small enough to read in one screen, and comment each override with *why*, not what. When a global setting is surprising in effect (isolation mode is the classic), prefer making it explicit per spec even at the cost of repetition. ## System properties and kotest.properties Several settings can also be provided outside the code — via JVM system properties or a `kotest.properties` file on the classpath. That is how a CI job tightens or loosens a run (for example forcing a stricter or looser configuration for one pipeline) without editing the config class. Treat it as an operational lever, not the primary home of your configuration: settings expressed in two places at once are a debugging tax. ## The interview signal Strong answers name a handful of real settings, state the precedence order confidently, and then say something about *policy*: which settings should be global because uniformity is the value, and which should be local because the exception carries information.
- Your project config sets a 30-second timeout but one test still runs for five minutes before failing. What are the candidate explanations?Most likely a more specific declaration wins — the spec's defaultTestConfig or that test's .config(timeout = ...). Next, the config class itself may not be detected for that module's run. Finally, check whether the code under test blocks in a way the timeout mechanism cannot interrupt promptly, which shows up as an overshoot rather than a clean cut-off.
- Which project-level settings would you argue should almost always be set, and why?failOnEmptyTestSuite, so a filter that matches nothing fails instead of going green; a duplicate-test-name policy, because duplicate names silently hide tests and corrupt reports; and a default timeout, so a hung test fails in seconds rather than occupying CI. All three trade nothing away and convert silent problems into loud ones, which is exactly what a global default is good at.
saying these in an interview costs you the question
- Believing a project default overrides a spec or per-test setting
- Treating a global timeout as an enforced maximum rather than a default
- Putting settings in project config that half the suite then overrides
- Assuming project config is visible to someone reading a failing spec
- Thinking there is one project config for an entire multi-module repository