or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

boolean-assertions.mdcollection-assertions.mdequality-assertions.mdexception-testing.mdframework-integration.mdindex.mdtest-annotations.mdtest-utilities.mdtype-null-assertions.md

test-annotations.mddocs/

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

```