In kotlin.test, which annotations mark a test method and its setup/teardown in common (multiplatform) code, and why aren't you using JUnit's @Test directly?
answer
- @Test / @BeforeTest / @AfterTest / @Ignore from kotlin.test
- Before/After run per-test, not per-class
- JUnit @Test is JVM-only
- common code -> platform-neutral annotations
- @Ignore reports as skipped
basics
~20 sUse @Test to mark a test, @BeforeTest to run setup before each test, and @AfterTest to run cleanup after each. They come from kotlin.test so the same code works on every platform, not just the JVM.
solid answer
~30 sIn common Kotlin code you import @Test, @BeforeTest, @AfterTest, and @Ignore from the kotlin.test package. @Test marks a function as a test, @BeforeTest runs before every test in the class, @AfterTest runs after every test, and @Ignore skips a test. You don't use org.junit.jupiter.api.Test directly because JUnit only exists on the JVM; common code compiles to JVM, JS, and Native, so it must reference platform-neutral annotations. kotlin.test declares these as expect annotations and, per target, maps (typealiases) them to the real framework — JUnit on JVM, Mocha/Jasmine-style on JS, the Kotlin/Native test runner on Native. Same source, many runners.
code
kotlin · 16 linesimport kotlin.test.Test
import kotlin.test.BeforeTest
import kotlin.test.AfterTest
import kotlin.test.assertEquals
class StackTest {
private lateinit var stack: ArrayDeque<Int>
@BeforeTest fun init() { stack = ArrayDeque() }
@AfterTest fun clear() { stack.clear() }
@Test fun pushThenPop() {
stack.addLast(7)
assertEquals(7, stack.removeLast())
}
}go deeper
Names the four annotations and knows they come from kotlin.test, not JUnit.
Explains per-test lifecycle and why common code can't import JUnit directly.
Articulates the expect/typealias mapping to JUnit on JVM and other runners per target.
Frames it as the design pattern for platform-neutral test APIs and the trade-offs of the lowest-common-denominator surface.
## The problem kotlin.test solves Kotlin Multiplatform (KMP) lets you write code once in a **common** source set and compile it to several **targets**: JVM, JS, Native, Wasm. Test frameworks are platform-specific — JUnit is JVM-only, JS uses Mocha/Jasmine-style runners, Native has its own runner. If your common tests imported `org.junit.jupiter.api.Test`, they would not compile for JS or Native. `kotlin.test` is a small, platform-neutral test API shipped with Kotlin. In common code you import its annotations and they get **mapped per target** to the underlying framework. ## The four core annotations - **`@Test`** — marks a function (no parameters, returns Unit) as a test case. - **`@BeforeTest`** — marks a function to run **before each** test in the class (fresh setup per test). - **`@AfterTest`** — marks a function to run **after each** test (cleanup), even if the test failed. - **`@Ignore`** — skips a test (or a whole class) so it is reported as skipped, not run. ```kotlin import kotlin.test.Test import kotlin.test.BeforeTest import kotlin.test.AfterTest import kotlin.test.Ignore import kotlin.test.assertEquals class CalculatorTest { private lateinit var calc: Calculator @BeforeTest fun setUp() { calc = Calculator() } @AfterTest fun tearDown() { calc.close() } @Test fun adds() { assertEquals(4, calc.add(2, 2)) } @Ignore @Test fun notReadyYet() { /* skipped */ } } ``` ## Why not JUnit's @Test directly JUnit's `@Test` lives in `org.junit.jupiter.api` (JUnit 5) or `org.junit` (JUnit 4) — both **JVM-only**. Common code must stay platform-neutral, so it references `kotlin.test` annotations instead. On the JVM, kotlin.test's `@Test` is **typealiased to JUnit's** annotation under the hood, so a JUnit runner still discovers and runs your tests. ## Key point: per-test lifecycle Unlike `@BeforeClass`/`@AfterClass` (once per class), `@BeforeTest`/`@AfterTest` run **once per test method**, giving each test a clean fixture.
- How often do @BeforeTest and @AfterTest run for a class with three @Test methods?Each runs three times — once before and once after each individual test. They are per-test, not per-class.
- Does @AfterTest still run if the test throws?Yes. Teardown runs regardless of whether the test passed, failed an assertion, or threw — it mirrors JUnit's @AfterEach behavior.
kotlin.test is a universal power adapter: you plug into one socket (@Test) and it fits whatever outlet (JUnit/JS/Native) the target country uses.
saying these in an interview costs you the question
- Importing org.junit.jupiter.api.Test in a common source set
- Thinking @BeforeTest runs once per class like @BeforeClass
- Believing @Ignore deletes or fails the test instead of skipping it
- Saying kotlin.test only works on the JVM