0
# Test Annotations
1
2
Platform-agnostic test annotations that map to appropriate testing frameworks on each Kotlin compilation target. These annotations provide a unified way to mark test functions and test lifecycle methods across JVM, JavaScript, Native, and other Kotlin platforms.
3
4
## Capabilities
5
6
### @Test
7
8
Marks a function as a test case that should be executed by the testing framework.
9
10
```kotlin { .api }
11
/**
12
* Marks a function as a test case.
13
* The annotated function will be discovered and executed by the testing framework.
14
* On JVM, maps to JUnit's @Test or TestNG's @Test.
15
* On JavaScript, integrates with Mocha, Jest, or other JS testing frameworks.
16
* On Native, works with the native testing infrastructure.
17
*/
18
@Target(AnnotationTarget.FUNCTION)
19
annotation class Test
20
```
21
22
**Usage Examples:**
23
24
```kotlin
25
import kotlin.test.Test
26
import kotlin.test.assertEquals
27
import kotlin.test.assertTrue
28
29
class UserServiceTest {
30
31
@Test
32
fun testCreateUser() {
33
val userService = UserService()
34
val user = userService.createUser("Alice", "alice@example.com")
35
36
assertEquals("Alice", user.name)
37
assertEquals("alice@example.com", user.email)
38
assertTrue(user.id > 0)
39
}
40
41
@Test
42
fun testValidateEmail() {
43
val userService = UserService()
44
45
assertTrue(userService.isValidEmail("test@example.com"))
46
assertFalse(userService.isValidEmail("invalid-email"))
47
assertFalse(userService.isValidEmail(""))
48
}
49
50
@Test
51
fun testUserAgeValidation() {
52
val userService = UserService()
53
54
// Test valid ages
55
assertTrue(userService.isValidAge(18))
56
assertTrue(userService.isValidAge(65))
57
58
// Test invalid ages
59
assertFalse(userService.isValidAge(-1))
60
assertFalse(userService.isValidAge(0))
61
assertFalse(userService.isValidAge(150))
62
}
63
}
64
```
65
66
### @Ignore
67
68
Marks a test or test class as ignored, preventing it from being executed.
69
70
```kotlin { .api }
71
/**
72
* Marks a test function or test class as ignored.
73
* Ignored tests are not executed but are typically reported as skipped.
74
* Can be applied to individual test functions or entire test classes.
75
* On JVM, maps to JUnit's @Ignore or TestNG's enabled=false.
76
* On JavaScript and Native, integrates with platform-specific skip mechanisms.
77
*/
78
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
79
annotation class Ignore
80
```
81
82
**Usage Examples:**
83
84
```kotlin
85
import kotlin.test.*
86
87
class DatabaseTest {
88
89
@Test
90
fun testDatabaseConnection() {
91
// This test runs normally
92
val connection = Database.connect()
93
assertTrue(connection.isConnected())
94
}
95
96
@Test
97
@Ignore // Temporarily disable this test
98
fun testSlowDatabaseQuery() {
99
// This test is skipped during execution
100
val results = Database.executeSlowQuery()
101
assertTrue(results.isNotEmpty())
102
}
103
104
@Test
105
@Ignore // Disable until bug #123 is fixed
106
fun testBuggyFeature() {
107
// This test won't run until the @Ignore is removed
108
val result = BuggyService.processData()
109
assertNotNull(result)
110
}
111
}
112
113
// Ignore an entire test class
114
@Ignore
115
class ExperimentalFeatureTest {
116
117
@Test
118
fun testExperimentalFeature1() {
119
// None of the tests in this class will run
120
}
121
122
@Test
123
fun testExperimentalFeature2() {
124
// This is also ignored due to class-level annotation
125
}
126
}
127
128
class PartiallyDisabledTest {
129
130
@Test
131
fun testWorkingFeature() {
132
// This test runs normally
133
assertTrue(WorkingService.isAvailable())
134
}
135
136
@Test
137
@Ignore // Only this specific test is ignored
138
fun testBrokenFeature() {
139
// This specific test is skipped
140
BrokenService.process()
141
}
142
}
143
```
144
145
### @BeforeTest
146
147
Marks a function to be invoked before each test method in the class.
148
149
```kotlin { .api }
150
/**
151
* Marks a function to be invoked before each test method execution.
152
* The annotated function is called once before every @Test method in the same class.
153
* Used for test setup, initialization, and preparing test fixtures.
154
* On JVM, maps to JUnit's @Before or TestNG's @BeforeMethod.
155
* On JavaScript and Native, integrates with platform-specific setup mechanisms.
156
*/
157
@Target(AnnotationTarget.FUNCTION)
158
annotation class BeforeTest
159
```
160
161
### @AfterTest
162
163
Marks a function to be invoked after each test method in the class.
164
165
```kotlin { .api }
166
/**
167
* Marks a function to be invoked after each test method execution.
168
* The annotated function is called once after every @Test method in the same class.
169
* Used for test cleanup, resource disposal, and restoring test state.
170
* On JVM, maps to JUnit's @After or TestNG's @AfterMethod.
171
* On JavaScript and Native, integrates with platform-specific cleanup mechanisms.
172
*/
173
@Target(AnnotationTarget.FUNCTION)
174
annotation class AfterTest
175
```
176
177
**Combined Setup/Teardown Examples:**
178
179
```kotlin
180
import kotlin.test.*
181
182
class FileProcessorTest {
183
private lateinit var tempFile: File
184
private lateinit var processor: FileProcessor
185
186
@BeforeTest
187
fun setUp() {
188
// Called before each individual test method
189
tempFile = createTempFile("test", ".txt")
190
processor = FileProcessor()
191
192
// Initialize test data
193
tempFile.writeText("Initial test content")
194
195
println("Test setup completed for: ${getCurrentTestMethod()}")
196
}
197
198
@AfterTest
199
fun tearDown() {
200
// Called after each individual test method
201
if (::tempFile.isInitialized && tempFile.exists()) {
202
tempFile.delete()
203
}
204
205
processor.cleanup()
206
207
println("Test cleanup completed for: ${getCurrentTestMethod()}")
208
}
209
210
@Test
211
fun testFileRead() {
212
// setUp() called automatically before this
213
val content = processor.readFile(tempFile)
214
assertEquals("Initial test content", content)
215
// tearDown() called automatically after this
216
}
217
218
@Test
219
fun testFileWrite() {
220
// setUp() called automatically before this (fresh tempFile)
221
processor.writeFile(tempFile, "New content")
222
val content = tempFile.readText()
223
assertEquals("New content", content)
224
// tearDown() called automatically after this
225
}
226
227
@Test
228
fun testFileAppend() {
229
// setUp() called automatically before this (fresh tempFile)
230
processor.appendToFile(tempFile, " - appended")
231
val content = tempFile.readText()
232
assertEquals("Initial test content - appended", content)
233
// tearDown() called automatically after this
234
}
235
}
236
237
class DatabaseConnectionTest {
238
private lateinit var connection: DatabaseConnection
239
private lateinit var testDatabase: String
240
241
@BeforeTest
242
fun initializeDatabase() {
243
// Create a fresh test database for each test
244
testDatabase = "test_db_${System.currentTimeMillis()}"
245
connection = DatabaseConnection.create(testDatabase)
246
connection.connect()
247
248
// Set up test schema
249
connection.execute("CREATE TABLE users (id INT, name VARCHAR(100))")
250
connection.execute("INSERT INTO users VALUES (1, 'Test User')")
251
}
252
253
@AfterTest
254
fun cleanupDatabase() {
255
// Clean up after each test
256
if (::connection.isInitialized && connection.isConnected()) {
257
connection.execute("DROP TABLE IF EXISTS users")
258
connection.disconnect()
259
}
260
261
// Remove test database
262
DatabaseUtils.dropDatabase(testDatabase)
263
}
264
265
@Test
266
fun testSelectUser() {
267
val users = connection.query("SELECT * FROM users")
268
assertEquals(1, users.size)
269
assertEquals("Test User", users[0]["name"])
270
}
271
272
@Test
273
fun testInsertUser() {
274
connection.execute("INSERT INTO users VALUES (2, 'Another User')")
275
val users = connection.query("SELECT * FROM users")
276
assertEquals(2, users.size)
277
}
278
279
@Test
280
@Ignore // Database is slow in CI environment
281
fun testComplexQuery() {
282
// This test is ignored but would still get setUp/tearDown if it ran
283
val result = connection.query("SELECT COUNT(*) FROM users WHERE name LIKE '%Test%'")
284
assertEquals(1, result[0]["count"])
285
}
286
}
287
```
288
289
## Execution Order and Lifecycle
290
291
The test annotations follow a predictable execution order:
292
293
1. **Class Initialization**: Test class is instantiated
294
2. **@BeforeTest**: Setup method is called
295
3. **@Test**: Individual test method is executed
296
4. **@AfterTest**: Cleanup method is called
297
5. **Repeat**: Steps 2-4 repeat for each test method
298
299
```kotlin
300
class TestLifecycleExample {
301
302
init {
303
println("1. Test class constructor called")
304
}
305
306
@BeforeTest
307
fun setup() {
308
println("2. @BeforeTest: Setting up for test")
309
}
310
311
@Test
312
fun testA() {
313
println("3. @Test: Running testA")
314
}
315
316
@Test
317
fun testB() {
318
println("3. @Test: Running testB")
319
}
320
321
@AfterTest
322
fun cleanup() {
323
println("4. @AfterTest: Cleaning up after test")
324
}
325
}
326
327
// Output would be:
328
// 1. Test class constructor called
329
// 2. @BeforeTest: Setting up for test
330
// 3. @Test: Running testA
331
// 4. @AfterTest: Cleaning up after test
332
// 2. @BeforeTest: Setting up for test
333
// 3. @Test: Running testB
334
// 4. @AfterTest: Cleaning up after test
335
```
336
337
## Platform-Specific Behavior
338
339
### JVM Platform
340
- **@Test**: Maps to JUnit 4/5 `@Test` or TestNG `@Test`
341
- **@Ignore**: Maps to JUnit `@Ignore` or TestNG `enabled=false`
342
- **@BeforeTest**: Maps to JUnit `@Before`/`@BeforeEach` or TestNG `@BeforeMethod`
343
- **@AfterTest**: Maps to JUnit `@After`/`@AfterEach` or TestNG `@AfterMethod`
344
345
### JavaScript Platform
346
- **@Test**: Integrates with Mocha `it()`, Jest `test()`, or other JS testing frameworks
347
- **@Ignore**: Uses framework-specific skip mechanisms (`it.skip()`, `test.skip()`)
348
- **@BeforeTest/@AfterTest**: Maps to `beforeEach()`/`afterEach()` hooks
349
350
### Native Platform
351
- **@Test**: Integrates with native testing infrastructure
352
- **@Ignore**: Platform-specific test skipping
353
- **@BeforeTest/@AfterTest**: Native setup/teardown mechanisms
354
355
## Best Practices
356
357
### Setup and Cleanup
358
Always pair resource acquisition with proper cleanup:
359
360
```kotlin
361
@BeforeTest
362
fun setup() {
363
// Acquire resources
364
database = TestDatabase.create()
365
httpServer = TestServer.start(port = 8080)
366
}
367
368
@AfterTest
369
fun cleanup() {
370
// Always clean up, even if test fails
371
database?.close()
372
httpServer?.stop()
373
}
374
```
375
376
### Ignore Usage
377
Use meaningful comments when ignoring tests:
378
379
```kotlin
380
@Test
381
@Ignore // TODO: Re-enable after fixing issue #456 - NullPointerException in edge case
382
fun testEdgeCase() {
383
// Test implementation
384
}
385
```
386
387
### Test Independence
388
Ensure tests don't depend on execution order:
389
390
```kotlin
391
@BeforeTest
392
fun resetState() {
393
// Reset to known state before each test
394
GlobalState.reset()
395
TestData.clear()
396
}
397
```