or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-assertions.mdcore-assertions.mdexception-handling.mdhamcrest-matchers.mdindex.mdtest-assumptions.mdtest-lifecycle.mdtest-runners.md

test-lifecycle.mddocs/

0

# Test Lifecycle

1

2

JUnit provides annotations to control test execution flow, allowing setup and teardown operations at both method and class levels. These annotations ensure tests run in isolation with proper initialization and cleanup.

3

4

## Core Test Annotations

5

6

### @Test Annotation

7

8

```scala { .api }

9

class Test(

10

expected: Class[_ <: Throwable] = classOf[Test.None],

11

timeout: Long = 0L

12

) extends StaticAnnotation

13

14

object Test {

15

final class None private () extends Throwable

16

}

17

```

18

19

**Basic Usage:**

20

```scala

21

class UserServiceTest {

22

@Test

23

def shouldCreateNewUser(): Unit = {

24

val user = userService.createUser("Alice", "alice@example.com")

25

assertNotNull(user)

26

assertEquals("Alice", user.getName())

27

}

28

}

29

```

30

31

**Expected Exception Testing:**

32

```scala

33

@Test(expected = classOf[IllegalArgumentException])

34

def shouldRejectInvalidEmail(): Unit = {

35

userService.createUser("Bob", "invalid-email")

36

}

37

```

38

39

**Timeout Testing:**

40

```scala

41

@Test(timeout = 1000L) // 1 second timeout

42

def shouldCompleteQuickly(): Unit = {

43

val result = quickOperation()

44

assertNotNull(result)

45

}

46

```

47

48

**Combined Parameters:**

49

```scala

50

@Test(expected = classOf[TimeoutException], timeout = 5000L)

51

def shouldTimeoutWithException(): Unit = {

52

slowOperationThatTimesOut()

53

}

54

```

55

56

## Method-Level Lifecycle

57

58

### @Before - Setup Before Each Test

59

60

```scala { .api }

61

class Before extends StaticAnnotation

62

```

63

64

**Usage:**

65

```scala

66

class DatabaseTest {

67

private var database: Database = _

68

69

@Before

70

def setUp(): Unit = {

71

database = new TestDatabase()

72

database.connect()

73

database.initializeSchema()

74

}

75

76

@Test

77

def shouldInsertUser(): Unit = {

78

val user = User("Alice", "alice@example.com")

79

database.insert(user)

80

assertEquals(1, database.count())

81

}

82

83

@Test

84

def shouldUpdateUser(): Unit = {

85

val user = User("Bob", "bob@example.com")

86

database.insert(user)

87

database.update(user.copy(email = "bob.smith@example.com"))

88

assertEquals("bob.smith@example.com", database.findById(user.id).email)

89

}

90

}

91

```

92

93

### @After - Cleanup After Each Test

94

95

```scala { .api }

96

class After extends StaticAnnotation

97

```

98

99

**Usage:**

100

```scala

101

class FileSystemTest {

102

private var tempDir: Path = _

103

104

@Before

105

def createTempDirectory(): Unit = {

106

tempDir = Files.createTempDirectory("test")

107

}

108

109

@After

110

def cleanupTempDirectory(): Unit = {

111

if (tempDir != null) {

112

Files.walk(tempDir)

113

.sorted(java.util.Comparator.reverseOrder())

114

.forEach(Files.delete)

115

}

116

}

117

118

@Test

119

def shouldCreateFile(): Unit = {

120

val file = tempDir.resolve("test.txt")

121

Files.write(file, "content".getBytes)

122

assertTrue(Files.exists(file))

123

}

124

}

125

```

126

127

## Class-Level Lifecycle

128

129

### @BeforeClass - One-Time Setup

130

131

```scala { .api }

132

class BeforeClass extends StaticAnnotation

133

```

134

135

**Usage:**

136

```scala

137

class IntegrationTest {

138

companion object {

139

private var server: TestServer = _

140

141

@BeforeClass

142

def startServer(): Unit = {

143

server = new TestServer(8080)

144

server.start()

145

// Wait for server to be ready

146

Thread.sleep(1000)

147

}

148

149

@AfterClass

150

def stopServer(): Unit = {

151

if (server != null) {

152

server.stop()

153

}

154

}

155

}

156

157

@Test

158

def shouldRespondToHealthCheck(): Unit = {

159

val response = httpClient.get("http://localhost:8080/health")

160

assertEquals(200, response.getStatusCode())

161

}

162

}

163

```

164

165

### @AfterClass - One-Time Cleanup

166

167

```scala { .api }

168

class AfterClass extends StaticAnnotation

169

```

170

171

Methods annotated with @AfterClass are executed once after all test methods in the class have run.

172

173

## Test Exclusion

174

175

### @Ignore - Skip Tests

176

177

```scala { .api }

178

class Ignore(value: String = "") extends StaticAnnotation

179

```

180

181

**Usage:**

182

```scala

183

class FeatureTest {

184

@Test

185

def shouldWork(): Unit = {

186

// This test runs normally

187

assertTrue(feature.isEnabled())

188

}

189

190

@Ignore("Feature not implemented yet")

191

@Test

192

def shouldHandleAdvancedCase(): Unit = {

193

// This test is skipped

194

feature.advancedOperation()

195

}

196

197

@Ignore // No reason provided

198

@Test

199

def temporarilyDisabled(): Unit = {

200

// This test is also skipped

201

flakeyOperation()

202

}

203

}

204

```

205

206

**Ignoring Entire Test Classes:**

207

```scala

208

@Ignore("Integration tests disabled in CI")

209

class SlowIntegrationTest {

210

@Test

211

def shouldConnectToExternalService(): Unit = {

212

// All tests in this class are skipped

213

}

214

}

215

```

216

217

## Rule Support Annotations

218

219

### @Rule - Method-Level Rules

220

221

```scala { .api }

222

trait Rule extends Annotation

223

```

224

225

**Usage:**

226

```scala

227

class RuleTest {

228

@Rule

229

val tempFolder: TemporaryFolder = new TemporaryFolder()

230

231

@Rule

232

val timeout: Timeout = new Timeout(10000) // 10 second timeout for all tests

233

234

@Test

235

def shouldUseTemporaryFolder(): Unit = {

236

val file = tempFolder.newFile("test.txt")

237

Files.write(file.toPath, "content".getBytes)

238

assertTrue(file.exists())

239

}

240

}

241

```

242

243

### @ClassRule - Class-Level Rules

244

245

```scala { .api }

246

trait ClassRule extends Annotation

247

```

248

249

**Usage:**

250

```scala

251

class ClassRuleTest {

252

companion object {

253

@ClassRule

254

val externalResource: ExternalResource = new ExternalResource() {

255

override def before(): Unit = {

256

// Setup expensive resource once for all tests

257

println("Setting up external resource")

258

}

259

260

override def after(): Unit = {

261

// Cleanup expensive resource after all tests

262

println("Cleaning up external resource")

263

}

264

}

265

}

266

267

@Test

268

def shouldUseExternalResource(): Unit = {

269

// Use the external resource

270

assertTrue(externalResource.isAvailable())

271

}

272

}

273

```

274

275

## Execution Order

276

277

JUnit executes lifecycle methods in the following order:

278

279

1. **@BeforeClass methods** (once per class)

280

2. For each test method:

281

- **@Before methods** (before each test)

282

- **@Test method** (the actual test)

283

- **@After methods** (after each test, even if test fails)

284

3. **@AfterClass methods** (once per class, even if tests fail)

285

286

**Exception Handling:**

287

- If @Before fails, the test method is skipped but @After still runs

288

- If @Test fails, @After still runs

289

- If @After fails, it doesn't affect other tests

290

- @AfterClass always runs even if @BeforeClass or tests fail

291

292

## Best Practices

293

294

1. **Resource Management**: Always pair setup with cleanup (@Before with @After, @BeforeClass with @AfterClass)

295

296

2. **Exception Safety**: Use try-finally or similar patterns in @After methods to ensure cleanup

297

298

3. **Test Isolation**: Each test should be independent - @Before and @After ensure clean state

299

300

4. **Expensive Resources**: Use @BeforeClass/@AfterClass for expensive setup (database connections, web servers)

301

302

5. **Temporary Resources**: Use @Rule with TemporaryFolder, TestName, or custom rules for common patterns