What is Kotest's AnnotationSpec, and why is it normally recommended as a migration step rather than a long-term style choice?
answer
- tenth style, annotation-driven, not a DSL
- @Test, @BeforeEach/@AfterEach, @BeforeAll/@AfterAll, @Ignore
- annotations are Kotest's own declarations
- migration bridge: base class + imports
- loses free-text names and nesting
basics
~20 sAnnotationSpec lets you write annotation-driven test classes - methods marked @Test, with @BeforeEach/@AfterEach/@BeforeAll/@AfterAll and @Ignore - using Kotest's own annotations. It exists so annotation-style suites can move onto the Kotest runtime with minimal edits; it gives up free-text names and nesting.
solid answer
~50 sAnnotationSpec is one of Kotest's ten spec styles, and the only one that is not a lambda DSL. You extend AnnotationSpec and write methods annotated with Kotest's own @Test, plus @BeforeEach/@AfterEach, @BeforeAll/@AfterAll and @Ignore. The annotations are declared by Kotest itself, and the class runs on the Kotest engine. Its purpose is mechanical migration: an existing annotation-driven Kotlin test class can be converted largely by changing the base class and the imports, after which it immediately gets the Kotest runtime - matchers, extensions, listeners, property testing, the same reporting. It is a stepping stone because of what it cannot express: test names are method identifiers, so no free-text sentences; there are no containers, so no nesting or per-container setup scoping; and the descriptive DSL that makes the other styles readable is absent. The usual plan is to convert wholesale to AnnotationSpec to get onto one engine, then rewrite file by file into a DSL style.
code
kotlin · 14 linesclass BasketTest : AnnotationSpec() {
@BeforeEach
fun reset() { store.clear() }
@Test
fun emptyBasketTotalsZero() {
Basket().total() shouldBe 0
}
@Ignore
@Test
fun discountsStack() { }
}go deeper
Know it is the annotation-based Kotest style with @Test and before/after annotations, used when coming from annotation-driven tests.
Explain the annotation set, that conversion is a base-class and import change, and the two capabilities lost: naming and nesting.
Lay out the phased migration and what each phase buys, and argue for banning new AnnotationSpec files.
Frame it as decoupling engine unification from test rewriting, with review policy and an exit condition so the bridge does not become permanent.
## What it is Kotest ships ten spec styles. Nine are lambda DSLs (FunSpec, StringSpec, ShouldSpec, DescribeSpec, BehaviorSpec, FeatureSpec, WordSpec, FreeSpec, ExpectSpec). AnnotationSpec is the tenth and the odd one out: tests are methods on the class, marked with annotations. The annotations Kotest provides for it cover the familiar set: @Test to mark a test method, @BeforeEach and @AfterEach for per-test callbacks, @BeforeAll and @AfterAll for per-class ones, and @Ignore to disable a method. They are Kotest's own declarations, so what runs the class is the Kotest engine and everything else in the Kotest runtime is available inside those methods. ## Why the style exists Adopting a new test framework across a large codebase is usually blocked by volume, not by disagreement. AnnotationSpec removes the volume problem: the shape of an annotation-driven test class survives, so conversion is mostly a base-class change and an import change rather than a rewrite of every test body. Once converted, the suite runs on one engine, and the team gets Kotest matchers, extensions and configuration immediately instead of after a multi-month rewrite. That is a real strategic value: it decouples 'stop running two frameworks' from 'rewrite the tests', which are otherwise the same enormous task. ## Why it is not the destination Three capabilities are missing compared with the DSL styles. 1. Naming. The test name is the method identifier, so you are back to encoding sentences as identifiers. Every argument for Kotest's free-text sentence names - readable reports, prose that non-engineers can follow - is unavailable here. 2. Structure. There are no containers, so no nesting, no grouping in the report tree, and no way to scope setup to a subset of tests. All setup is per-method or per-class. 3. Expressiveness. Registering tests from data, computing names, or wrapping a group of tests in shared context are all things a lambda DSL does naturally and a method-per-test class cannot. ## The usual migration shape - Phase 1: switch specs to AnnotationSpec mechanically. Goal: one engine, one report, no behaviour change. Keep the diff boring so it is reviewable in bulk. - Phase 2: adopt Kotest matchers inside those bodies. This is independent of style and gives immediate readability wins. - Phase 3: rewrite into the chosen DSL style opportunistically - whenever a test area is being changed anyway - rather than as a big-bang commit. Nesting, sentence names and data-driven registration arrive with this phase. - Phase 4: treat new AnnotationSpec files as an exception that needs justification, and eventually forbid them in review. ## How to talk about it in an interview The strong answer names the tradeoff explicitly: AnnotationSpec buys engine unification at near-zero rewrite cost, and pays with naming and structure. The weak answer treats it as just another style you might prefer for taste. It is also worth saying what you would not do - leaving a codebase permanently split between AnnotationSpec and a DSL style gives you the worst of both: two idioms to read, and none of the expressiveness that motivated the move.
- What do you gain immediately after converting a suite to AnnotationSpec, before rewriting anything?One test engine and one report for the whole suite, plus access to the whole Kotest runtime inside existing test bodies - matchers, extensions, listeners, property testing and Kotest's configuration surface. The tests themselves have not improved, but the platform question is settled and further improvement becomes incremental.
- Would you allow new tests to be written as AnnotationSpec?Normally no. The style's value is that it makes an existing corpus cheap to move; a new test has no legacy shape to preserve, so writing it in the team's chosen DSL style costs nothing extra and gains sentence names and nesting. A review rule that flags new AnnotationSpec files keeps the migration one-directional.
saying these in an interview costs you the question
- Claiming AnnotationSpec supports nested containers or free-text test names
- Presenting it as just a taste preference rather than a migration bridge
- Assuming Kotest matchers only work in DSL styles
- Planning a big-bang rewrite of every test into a DSL style in one commit