skip to content

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?

level: middleimportance: must knowfreq 40%

answer

  1. most specific wins: test > spec > project > framework
  2. isolationMode, timeout, assertionMode, testCaseOrder
  3. failOnEmptyTestSuite, duplicateTestNameMode
  4. extensions() + beforeProject/afterProject
  5. a default everyone overrides is the wrong default

basics

~10 s

Project 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 lines
kotlin
package 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

for a junior

Name a few settings that live in project config and know that per-test settings win over global ones.

for a middle

State the full precedence chain and give real examples of settings at each layer.

for a senior

Use precedence to diagnose 'setting ignored' reports and argue which defaults belong global versus local.

for a principal

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

context