0
# Fixtures and Lifecycle
1
2
ScalaTest provides comprehensive support for test fixtures and suite lifecycle management, enabling setup and teardown operations, resource management, and data sharing between tests.
3
4
## Capabilities
5
6
### Before and After Each Test
7
8
Execute setup and cleanup code before and after each individual test.
9
10
```scala { .api }
11
trait BeforeAndAfterEach extends SuiteMixin {
12
13
/**
14
* Execute before each test method
15
*/
16
def beforeEach(): Unit = ()
17
18
/**
19
* Execute after each test method
20
*/
21
def afterEach(): Unit = ()
22
23
/**
24
* Override to customize the execution around each test
25
*/
26
abstract override def withFixture(test: NoArgTest): Outcome = {
27
beforeEach()
28
try super.withFixture(test)
29
finally afterEach()
30
}
31
}
32
```
33
34
**Usage Examples:**
35
36
```scala
37
import org.scalatest.funsuite.AnyFunSuite
38
import org.scalatest.BeforeAndAfterEach
39
40
class DatabaseSuite extends AnyFunSuite with BeforeAndAfterEach {
41
var database: Database = _
42
43
override def beforeEach(): Unit = {
44
database = new Database()
45
database.connect()
46
database.createTables()
47
}
48
49
override def afterEach(): Unit = {
50
database.dropTables()
51
database.disconnect()
52
database = null
53
}
54
55
test("should insert user") {
56
val user = User("John", "john@example.com")
57
database.insert(user)
58
database.count("users") should equal (1)
59
}
60
61
test("should delete user") {
62
val user = User("Jane", "jane@example.com")
63
database.insert(user)
64
database.delete(user.id)
65
database.count("users") should equal (0)
66
}
67
}
68
```
69
70
### Before and After All Tests
71
72
Execute setup and cleanup code once before and after all tests in the suite.
73
74
```scala { .api }
75
trait BeforeAndAfterAll extends SuiteMixin {
76
77
/**
78
* Execute once before all tests in the suite
79
*/
80
def beforeAll(): Unit = ()
81
82
/**
83
* Execute once after all tests in the suite
84
*/
85
def afterAll(): Unit = ()
86
87
/**
88
* Override to customize suite-wide setup/teardown
89
*/
90
abstract override def run(testName: Option[String], args: Args): Status = {
91
if (!args.runTestInNewInstance) beforeAll()
92
try {
93
val status = super.run(testName, args)
94
status.waitUntilCompleted()
95
status
96
} finally {
97
if (!args.runTestInNewInstance) afterAll()
98
}
99
}
100
}
101
```
102
103
**Usage Examples:**
104
105
```scala
106
import org.scalatest.funsuite.AnyFunSuite
107
import org.scalatest.BeforeAndAfterAll
108
109
class ServerSuite extends AnyFunSuite with BeforeAndAfterAll {
110
var server: TestServer = _
111
112
override def beforeAll(): Unit = {
113
server = new TestServer(port = 8080)
114
server.start()
115
server.waitUntilReady()
116
}
117
118
override def afterAll(): Unit = {
119
server.stop()
120
server.waitUntilStopped()
121
}
122
123
test("server should respond to health check") {
124
val response = httpGet(s"http://localhost:8080/health")
125
response.status should equal (200)
126
response.body should include ("OK")
127
}
128
129
test("server should handle API requests") {
130
val response = httpPost(s"http://localhost:8080/api/users", """{"name": "test"}""")
131
response.status should equal (201)
132
}
133
}
134
```
135
136
### Fixture Test Suites
137
138
Share fixtures between tests using parameterized test functions.
139
140
```scala { .api }
141
trait FixtureTestSuite extends TestSuite {
142
143
/**
144
* The type of fixture object passed to tests
145
*/
146
type FixtureParam
147
148
/**
149
* Create and cleanup fixture for each test
150
*/
151
def withFixture(test: OneArgTest): Outcome
152
153
/**
154
* Run a single test with fixture
155
*/
156
def runTest(testName: String, args: Args): Status = {
157
val oneArgTest = new OneArgTest {
158
val name = testName
159
def apply(fixture: FixtureParam): Outcome = {
160
// Test implementation provided by concrete suite
161
}
162
}
163
withFixture(oneArgTest).toStatus
164
}
165
}
166
167
/**
168
* Fixture-enabled FunSuite
169
*/
170
abstract class FixtureAnyFunSuite extends FixtureTestSuite with TestRegistration {
171
172
/**
173
* Register a test that requires a fixture
174
*/
175
protected def test(testName: String)(testFun: FixtureParam => Any): Unit
176
}
177
```
178
179
**Usage Examples:**
180
181
```scala
182
import org.scalatest.fixture.AnyFunSuite
183
import java.io.{File, FileWriter}
184
185
class FileFixtureSuite extends AnyFunSuite {
186
187
// Fixture is a temporary file
188
type FixtureParam = File
189
190
def withFixture(test: OneArgTest): Outcome = {
191
val tempFile = File.createTempFile("test", ".txt")
192
193
try {
194
// Setup: write initial content
195
val writer = new FileWriter(tempFile)
196
writer.write("initial content")
197
writer.close()
198
199
// Run test with fixture
200
test(tempFile)
201
} finally {
202
// Cleanup: delete temp file
203
tempFile.delete()
204
}
205
}
206
207
test("should read file content") { file =>
208
val content = scala.io.Source.fromFile(file).mkString
209
content should include ("initial")
210
}
211
212
test("should append to file") { file =>
213
val writer = new FileWriter(file, true) // append mode
214
writer.write("\nappended content")
215
writer.close()
216
217
val content = scala.io.Source.fromFile(file).mkString
218
content should include ("appended")
219
}
220
}
221
```
222
223
### Fixture Context
224
225
Lightweight fixture sharing using traits.
226
227
```scala { .api }
228
trait FixtureContext {
229
// Define fixture data and helper methods as trait members
230
}
231
```
232
233
**Usage Examples:**
234
235
```scala
236
import org.scalatest.funsuite.AnyFunSuite
237
238
class ContextFixtureSuite extends AnyFunSuite {
239
240
trait DatabaseContext {
241
val database = new InMemoryDatabase()
242
database.createSchema()
243
244
val testUser = User("testuser", "test@example.com")
245
database.insert(testUser)
246
247
def findUser(name: String): Option[User] = database.findByName(name)
248
}
249
250
trait ApiContext {
251
val baseUrl = "http://test.example.com"
252
val apiKey = "test-api-key"
253
254
def makeRequest(endpoint: String): HttpResponse = {
255
// Mock HTTP request implementation
256
HttpResponse(200, s"Response from $endpoint")
257
}
258
}
259
260
test("should find existing user") {
261
new DatabaseContext {
262
findUser("testuser") should be (defined)
263
findUser("nonexistent") should be (empty)
264
}
265
}
266
267
test("should handle API requests") {
268
new ApiContext {
269
val response = makeRequest("/users")
270
response.status should equal (200)
271
response.body should include ("Response from /users")
272
}
273
}
274
275
test("should combine contexts") {
276
new DatabaseContext with ApiContext {
277
// Use both database and API fixtures
278
val user = findUser("testuser").get
279
val response = makeRequest(s"/users/${user.id}")
280
response.status should equal (200)
281
}
282
}
283
}
284
```
285
286
### Test Data and Config Map
287
288
Access configuration and test data through the Args parameter.
289
290
```scala { .api }
291
trait BeforeAndAfterEachTestData extends SuiteMixin {
292
293
/**
294
* Execute before each test with access to test data
295
*/
296
def beforeEach(testData: TestData): Unit = ()
297
298
/**
299
* Execute after each test with access to test data
300
*/
301
def afterEach(testData: TestData): Unit = ()
302
303
abstract override def withFixture(test: NoArgTest): Outcome = {
304
beforeEach(test)
305
try super.withFixture(test)
306
finally afterEach(test)
307
}
308
}
309
310
trait BeforeAndAfterAllConfigMap extends SuiteMixin {
311
312
/**
313
* Execute before all tests with access to config map
314
*/
315
def beforeAll(configMap: ConfigMap): Unit = ()
316
317
/**
318
* Execute after all tests with access to config map
319
*/
320
def afterAll(configMap: ConfigMap): Unit = ()
321
}
322
323
/**
324
* Test metadata and configuration
325
*/
326
trait TestData {
327
val name: String
328
val scopes: Vector[String]
329
val text: String
330
val tags: Set[String]
331
val pos: Option[source.Position]
332
}
333
334
/**
335
* Configuration map for test execution
336
*/
337
class ConfigMap(map: Map[String, Any]) {
338
def apply(key: String): Any = map(key)
339
def get(key: String): Option[Any] = map.get(key)
340
def contains(key: String): Boolean = map.contains(key)
341
def ++(other: ConfigMap): ConfigMap = new ConfigMap(map ++ other.map)
342
}
343
```
344
345
**Usage Examples:**
346
347
```scala
348
import org.scalatest.funsuite.AnyFunSuite
349
import org.scalatest.{BeforeAndAfterEachTestData, BeforeAndAfterAllConfigMap}
350
351
class ConfigurableSuite extends AnyFunSuite
352
with BeforeAndAfterEachTestData
353
with BeforeAndAfterAllConfigMap {
354
355
var globalConfig: String = _
356
357
override def beforeAll(configMap: ConfigMap): Unit = {
358
globalConfig = configMap.getOrElse("environment", "test").toString
359
println(s"Running tests in $globalConfig environment")
360
}
361
362
override def beforeEach(testData: TestData): Unit = {
363
println(s"Starting test: ${testData.name}")
364
if (testData.tags.contains("slow")) {
365
println("This is a slow test, please be patient...")
366
}
367
}
368
369
override def afterEach(testData: TestData): Unit = {
370
println(s"Completed test: ${testData.name}")
371
}
372
373
test("should use global config") {
374
globalConfig should not be empty
375
// Test implementation using globalConfig
376
}
377
378
test("slow database operation", SlowTest) {
379
// This test will get special logging due to the SlowTest tag
380
Thread.sleep(100) // Simulate slow operation
381
succeed
382
}
383
}
384
385
// Custom tag for marking slow tests
386
object SlowTest extends Tag("org.example.SlowTest")
387
```
388
389
## Types
390
391
```scala { .api }
392
/**
393
* Test function that receives no fixture parameter
394
*/
395
trait NoArgTest extends (() => Outcome) with TestData {
396
def apply(): Outcome
397
val name: String
398
val scopes: Vector[String]
399
val text: String
400
val tags: Set[String]
401
}
402
403
/**
404
* Test function that receives one fixture parameter
405
*/
406
trait OneArgTest extends (FixtureParam => Outcome) with TestData {
407
def apply(fixture: FixtureParam): Outcome
408
val name: String
409
val scopes: Vector[String]
410
val text: String
411
val tags: Set[String]
412
}
413
414
/**
415
* Mixin trait for suites that can be mixed into other suites
416
*/
417
trait SuiteMixin { this: Suite =>
418
// Mixed into Suite to provide additional functionality
419
}
420
421
/**
422
* Test outcome representing the result of running a test
423
*/
424
sealed abstract class Outcome extends Product with Serializable {
425
def isSucceeded: Boolean
426
def isFailed: Boolean
427
def isCanceled: Boolean
428
def isPending: Boolean
429
def toStatus: Status
430
}
431
432
case object Succeeded extends Outcome
433
final case class Failed(exception: Throwable) extends Outcome
434
final case class Canceled(exception: Throwable) extends Outcome
435
case object Pending extends Outcome
436
```