skip to content

Project-Level Config

A single AbstractProjectConfig subclass is auto-detected and sets suite-wide defaults — timeouts, isolation, assertion mode, parallelism, global extensions. Knowing what belongs at project vs spec vs test scope shows you can govern a large test suite, not just one file.

on this pageshow

explore

questions

5

How does Kotest 5 locate your AbstractProjectConfig class at runtime, and what would you check if its settings appear to be ignored?

level: middleimportance: must knowfreq 36%

answer

  1. subclass AbstractProjectConfig, no-arg constructor
  2. package io.kotest.provided (Kotest 5 convention)
  3. kotest.framework.config.fqn system property
  4. one config per run, per module classpath
  5. test source set, not main

basics

~20 s

Write one class (or object) extending AbstractProjectConfig with a no-arg constructor. Kotest 5 picks it up by convention from the io.kotest.provided package on the test classpath, or from the fully qualified name given in the kotest.framework.config.fqn system property. One config applies per run.

solid answer

~50 s

You declare configuration by subclassing `AbstractProjectConfig`: ```kotlin package io.kotest.provided import io.kotest.core.config.AbstractProjectConfig class ProjectConfig : AbstractProjectConfig() { override val isolationMode = IsolationMode.InstancePerLeaf } ``` Kotest 5 finds it by convention: a subclass in the **`io.kotest.provided`** package, on the test classpath of the module being executed. You can point at any class explicitly with the `kotest.framework.config.fqn` system property instead. Kotest uses one config per run. When settings seem ignored, check in this order: is the class in `io.kotest.provided` (or is the FQN property actually reaching the JVM running the tests)? Is it on the *test* classpath of the module whose tests are running — each module's run resolves its own config, so a config in module A does nothing for module B's tests? Does it have a usable no-arg constructor? And are you overriding the real property name rather than shadowing it with a new one?

code

kotlin · 11 lines
kotlin
package io.kotest.provided

import io.kotest.core.config.AbstractProjectConfig
import io.kotest.core.spec.IsolationMode
import kotlin.time.Duration.Companion.seconds

class ProjectConfig : AbstractProjectConfig() {
    override val isolationMode = IsolationMode.SingleInstance
    override val timeout = 30.seconds
    override fun extensions() = listOf(MyGlobalListener)
}

go deeper

for a junior

Know that global settings live in a class extending AbstractProjectConfig placed in the io.kotest.provided package.

for a middle

Add the FQN system property alternative and the one-config-per-run rule, plus the classpath/source-set checks.

for a senior

Debug systematically — package, module classpath, forked-JVM properties, constructibility — and separate detection failures from precedence effects.

for a principal

Set the repo-wide convention: thin per-module configs, shared settings in a test-support artefact, and documented rationale for every global default.

## What the class is `AbstractProjectConfig` is the single place to express run-wide test settings: default isolation mode, default timeouts, assertion mode, test ordering, concurrency knobs, globally registered extensions, and project-level `beforeProject`/`afterProject` callbacks. You subclass it and override the properties or functions you care about; everything you do not override keeps Kotest's default. ```kotlin package io.kotest.provided class ProjectConfig : AbstractProjectConfig() { override val isolationMode = IsolationMode.SingleInstance override val timeout = 30.seconds override fun extensions() = listOf(MyGlobalListener) override suspend fun beforeProject() { /* run-wide setup */ } } ``` The class name does not matter; the type and the location do. ## Detection in Kotest 5 Two mechanisms: 1. **Package convention.** Kotest 5 looks for a subclass of `AbstractProjectConfig` in the package `io.kotest.provided`. This is the scan-free path and the one to teach your team: put the file in a source directory whose package declaration is `io.kotest.provided`, in the test source set. 2. **Explicit FQN.** The system property `kotest.framework.config.fqn` names the config class directly, wherever it lives. Useful when you keep configuration in your own package structure, or when a shared test-support artefact supplies it. Either way, a single config is used for the run. Do not expect two config classes to merge — write one, and have it delegate to shared helpers if several teams contribute settings. ## Why it is a class and not a file Because configuration is code: you can compute a timeout from an environment variable, register different extensions on CI than locally, or build the extension list from a helper shared across modules. That expressiveness is the reason the config is not simply a properties file — though several settings can *also* be supplied through system properties or a `kotest.properties` file on the classpath, which is how CI often adjusts a run without editing code. ## Debugging "my config is ignored" Work through the chain, cheapest first. - **Package.** Is the declared package exactly `io.kotest.provided`? A file in a directory called `provided` with a different `package` line does not count — Kotest resolves by package, not by folder. - **Classpath and module.** Test runs resolve config from the classpath of the module being executed. In a multi-module repository, module B's tests do not see module A's config unless the config ships in an artefact that B depends on (and even then, only if it lands in `io.kotest.provided` or is named via the FQN property). - **Source set.** A config compiled into main rather than test may not be on the test run's classpath at all. - **Constructibility.** Kotest instantiates it reflectively; it needs to be public with a no-arg constructor (an `object` also works). - **The FQN property reaching the right JVM.** Setting a system property in a shell is not the same as it arriving in the forked JVM that runs the tests; verify with a `println(System.getProperty(...))` from a test if in doubt. - **Real override.** `override val isolationMode = …` fails to compile if the name is wrong, which is good — but a hand-written `val isolationMode` with no `override` (in an unrelated helper) silently does nothing. Prefer explicit `override`. - **Being overridden downstream.** A setting can be present and still not take effect because something more specific wins — a spec-level property or per-test `.config(...)`. Confirm by checking the spec before blaming detection. ## Practical conventions - Keep exactly one config class per module that runs tests, all in `io.kotest.provided`, all thin — a few overrides plus `extensions()` pointing at objects declared elsewhere. - Put shared behaviour in a test-support artefact and have each module's config register it, rather than trying to make one config serve every module. - Comment non-obvious global settings with *why*: a global default is invisible from the spec that it affects, so the reasoning must live somewhere a reader will find.

  • In a multi-module repository, does one AbstractProjectConfig cover every module's tests?
    No. Each module's test run resolves configuration from its own test classpath, so a config declared in one module does not govern another module's specs. The usual arrangement is a shared test-support artefact that exposes the extensions and defaults, plus a thin config class per module that registers them. Duplicating a handful of overrides per module is the price of independent module runs.
  • You set kotest.framework.config.fqn but the config still seems inert. What do you verify?
    That the property reaches the JVM actually executing the tests rather than the launching shell, that the fully qualified name is exact including the package, and that the named class is public with a no-arg constructor. Print the property from inside a test to confirm it arrived. If it is present and correct, the next suspect is precedence — a spec-level or per-test setting overriding the global one.

saying these in an interview costs you the question

  • Assuming any class extending AbstractProjectConfig anywhere is picked up automatically in Kotest 5
  • Putting the config in the main source set
  • Expecting two config classes to be merged
  • Believing one module's config governs the whole repository
  • Concluding detection failed when a spec-level override is actually winning

context

open as a page

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%

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.

open as a page

In a Kotest 5 spec, what does defaultTestConfig do, and how does it differ from calling .config(...) on an individual test?

level: middleimportance: should knowfreq 30%

basics

~10 s

defaultTestConfig 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.

open as a page

What does Kotest's assertionMode setting do when set to AssertionMode.Error in project config, and which test bugs does it catch?

level: seniorimportance: should knowfreq 24%

basics

~20 s

Kotest counts assertions made through its own assertion library during a test. With AssertionMode.Error, a test that completes without executing any counted assertion fails; Warn logs instead; None disables the check. It catches tests that verify nothing.

open as a page

You own the test suite for a large multi-team codebase using Kotest. How do you decide which settings belong in the project-wide AbstractProjectConfig versus in individual specs?

level: principalimportance: should knowfreq 20%

basics

~20 s

Globalise invariants that should be uniform and fail loudly — empty-suite failure, duplicate-name policy, assertion mode, a safety-net timeout, cross-cutting extensions. Keep locally anything whose exception carries information, and anything that changes semantics silently, such as isolation mode.

open as a page