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