0
# Fixtures
1
2
Flexible fixture system for managing test resources with both synchronous and asynchronous support. Fixtures provide setup and teardown functionality for tests that need external resources like databases, files, or network connections.
3
4
## Capabilities
5
6
### AnyFixture - Base Fixture Class
7
8
Base class for all fixture types, providing the core lifecycle methods for resource management.
9
10
```scala { .api }
11
/**
12
* Base class for all fixtures that manage test resources
13
* @param fixtureName Name identifier for the fixture
14
*/
15
abstract class AnyFixture[T](val fixtureName: String) {
16
17
/** Get the fixture value (the managed resource) */
18
def apply(): T
19
20
/** Setup performed once before all tests in the suite */
21
def beforeAll(): Any = ()
22
23
/** Setup performed before each individual test */
24
def beforeEach(context: BeforeEach): Any = ()
25
26
/** Cleanup performed after each individual test */
27
def afterEach(context: AfterEach): Any = ()
28
29
/** Cleanup performed once after all tests in the suite */
30
def afterAll(): Any = ()
31
}
32
```
33
34
### Fixture - Synchronous Fixture
35
36
Fixture for synchronous resource management where setup and teardown operations complete immediately.
37
38
```scala { .api }
39
/**
40
* Synchronous fixture for resources that don't require async operations
41
* @param name The fixture name
42
*/
43
abstract class Fixture[T](name: String) extends AnyFixture[T](name) {
44
45
/** Synchronous setup before all tests */
46
override def beforeAll(): Unit = ()
47
48
/** Synchronous setup before each test */
49
override def beforeEach(context: BeforeEach): Unit = ()
50
51
/** Synchronous cleanup after each test */
52
override def afterEach(context: AfterEach): Unit = ()
53
54
/** Synchronous cleanup after all tests */
55
override def afterAll(): Unit = ()
56
}
57
```
58
59
**Usage Examples:**
60
61
```scala
62
class DatabaseFixture extends Fixture[Database]("database") {
63
private var db: Database = _
64
65
def apply(): Database = db
66
67
override def beforeAll(): Unit = {
68
db = Database.createInMemory()
69
db.migrate()
70
}
71
72
override def afterAll(): Unit = {
73
db.close()
74
}
75
76
override def beforeEach(context: BeforeEach): Unit = {
77
db.clearAll()
78
db.seedTestData()
79
}
80
}
81
82
class DatabaseTests extends FunSuite {
83
val database = new DatabaseFixture()
84
override def munitFixtures = List(database)
85
86
test("user creation") {
87
val db = database()
88
val user = db.createUser("Alice", "alice@example.com")
89
assertEquals(user.name, "Alice")
90
}
91
92
test("user lookup") {
93
val db = database()
94
db.createUser("Bob", "bob@example.com")
95
val found = db.findUserByEmail("bob@example.com")
96
assert(found.isDefined)
97
}
98
}
99
```
100
101
### FutureFixture - Asynchronous Fixture
102
103
Fixture for asynchronous resource management where setup and teardown operations return `Future[Unit]`.
104
105
```scala { .api }
106
/**
107
* Asynchronous fixture for resources requiring async setup/teardown
108
* @param name The fixture name
109
*/
110
abstract class FutureFixture[T](name: String) extends AnyFixture[T](name) {
111
112
/** Asynchronous setup before all tests */
113
override def beforeAll(): Future[Unit] = Future.successful(())
114
115
/** Asynchronous setup before each test */
116
override def beforeEach(context: BeforeEach): Future[Unit] = Future.successful(())
117
118
/** Asynchronous cleanup after each test */
119
override def afterEach(context: AfterEach): Future[Unit] = Future.successful(())
120
121
/** Asynchronous cleanup after all tests */
122
override def afterAll(): Future[Unit] = Future.successful(())
123
}
124
```
125
126
**Usage Examples:**
127
128
```scala
129
import scala.concurrent.Future
130
import scala.concurrent.ExecutionContext.Implicits.global
131
132
class HttpServerFixture extends FutureFixture[HttpServer]("httpServer") {
133
private var server: HttpServer = _
134
135
def apply(): HttpServer = server
136
137
override def beforeAll(): Future[Unit] = {
138
server = new HttpServer()
139
server.start().map(_ => ())
140
}
141
142
override def afterAll(): Future[Unit] = {
143
server.stop()
144
}
145
146
override def beforeEach(context: BeforeEach): Future[Unit] = {
147
server.clearRoutes().map(_ => ())
148
}
149
}
150
151
class HttpServerTests extends FunSuite {
152
val httpServer = new HttpServerFixture()
153
override def munitFixtures = List(httpServer)
154
155
test("server responds to GET") {
156
val server = httpServer()
157
for {
158
_ <- server.addRoute("GET", "/hello", "Hello, World!")
159
response <- server.get("/hello")
160
} yield {
161
assertEquals(response.body, "Hello, World!")
162
}
163
}
164
}
165
```
166
167
### FunFixtures - Function-Style Fixtures
168
169
Function-style fixtures that provide a more flexible approach to resource management with per-test setup and teardown.
170
171
```scala { .api }
172
/**
173
* Trait providing function-style fixture support (mixed into BaseFunSuite)
174
*/
175
trait FunFixtures {
176
177
/**
178
* Function-style fixture that provides setup/teardown per test
179
*/
180
class FunFixture[T] private (
181
setup: TestOptions => Future[T],
182
teardown: T => Future[Unit]
183
) {
184
185
/** Define a test that uses this fixture */
186
def test(name: String)(body: T => Any)(implicit loc: Location): Unit
187
188
/** Define a test with options that uses this fixture */
189
def test(options: TestOptions)(body: T => Any)(implicit loc: Location): Unit
190
}
191
}
192
193
/**
194
* Factory methods for creating FunFixtures
195
*/
196
object FunFixture {
197
198
/** Create a synchronous fixture */
199
def apply[T](
200
setup: TestOptions => T,
201
teardown: T => Unit
202
): FunFixture[T]
203
204
/** Create an asynchronous fixture */
205
def async[T](
206
setup: TestOptions => Future[T],
207
teardown: T => Future[Unit]
208
): FunFixture[T]
209
210
/** Combine two fixtures into a tuple */
211
def map2[A, B](
212
a: FunFixture[A],
213
b: FunFixture[B]
214
): FunFixture[(A, B)]
215
216
/** Combine three fixtures into a tuple */
217
def map3[A, B, C](
218
a: FunFixture[A],
219
b: FunFixture[B],
220
c: FunFixture[C]
221
): FunFixture[(A, B, C)]
222
}
223
```
224
225
**Usage Examples:**
226
227
```scala
228
class FunFixtureExamples extends FunSuite {
229
230
// Simple synchronous fixture
231
val tempDir = FunFixture[Path](
232
setup = { _ => Files.createTempDirectory("test") },
233
teardown = { dir => Files.deleteRecursively(dir) }
234
)
235
236
tempDir.test("file operations") { dir =>
237
val file = dir.resolve("test.txt")
238
Files.write(file, "Hello, World!")
239
val content = Files.readString(file)
240
assertEquals(content, "Hello, World!")
241
}
242
243
// Asynchronous fixture
244
val httpClient = FunFixture.async[HttpClient](
245
setup = { _ => HttpClient.create() },
246
teardown = { client => client.close() }
247
)
248
249
httpClient.test("HTTP request") { client =>
250
client.get("https://api.example.com/status").map { response =>
251
assertEquals(response.status, 200)
252
}
253
}
254
255
// Fixture with test-specific setup
256
val database = FunFixture.async[Database](
257
setup = { testOptions =>
258
val dbName = s"test_${testOptions.name.replaceAll("\\s+", "_")}"
259
Database.create(dbName).map { db =>
260
db.migrate()
261
db
262
}
263
},
264
teardown = { db => db.drop() }
265
)
266
267
database.test("user operations") { db =>
268
for {
269
user <- db.createUser("Alice")
270
found <- db.findUser(user.id)
271
} yield {
272
assertEquals(found.map(_.name), Some("Alice"))
273
}
274
}
275
276
// Combined fixtures
277
val dbAndClient = FunFixture.map2(database, httpClient)
278
279
dbAndClient.test("integration test") { case (db, client) =>
280
for {
281
user <- db.createUser("Bob")
282
response <- client.post("/api/users", user.toJson)
283
} yield {
284
assertEquals(response.status, 201)
285
}
286
}
287
}
288
```
289
290
### Lifecycle Context
291
292
Context objects passed to fixture lifecycle methods containing information about the current test.
293
294
```scala { .api }
295
/**
296
* Context passed to beforeEach methods
297
* @param test The test that is about to run
298
*/
299
class BeforeEach(val test: Test) extends Serializable
300
301
/**
302
* Context passed to afterEach methods
303
* @param test The test that just completed
304
*/
305
class AfterEach(val test: Test) extends Serializable
306
```
307
308
**Usage Examples:**
309
310
```scala
311
class LoggingFixture extends Fixture[Logger]("logger") {
312
private val logger = Logger.getLogger("test")
313
314
def apply(): Logger = logger
315
316
override def beforeEach(context: BeforeEach): Unit = {
317
logger.info(s"Starting test: ${context.test.name}")
318
}
319
320
override def afterEach(context: AfterEach): Unit = {
321
logger.info(s"Completed test: ${context.afterEach.test.name}")
322
}
323
}
324
```
325
326
## Fixture Patterns
327
328
### Resource Pools
329
330
```scala
331
class ConnectionPoolFixture extends FutureFixture[ConnectionPool]("connectionPool") {
332
private var pool: ConnectionPool = _
333
334
def apply(): ConnectionPool = pool
335
336
override def beforeAll(): Future[Unit] = {
337
pool = ConnectionPool.create(maxConnections = 10)
338
pool.initialize()
339
}
340
341
override def afterAll(): Future[Unit] = {
342
pool.shutdown()
343
}
344
}
345
```
346
347
### External Services
348
349
```scala
350
class MockServerFixture extends Fixture[MockServer]("mockServer") {
351
private var server: MockServer = _
352
353
def apply(): MockServer = server
354
355
override def beforeAll(): Unit = {
356
server = MockServer.start(8080)
357
}
358
359
override def afterAll(): Unit = {
360
server.stop()
361
}
362
363
override def beforeEach(context: BeforeEach): Unit = {
364
server.reset() // Clear all mocked endpoints
365
}
366
}
367
```
368
369
### Test Data
370
371
```scala
372
val testData = FunFixture[TestData](
373
setup = { testOptions =>
374
val data = if (testOptions.tags.contains(new Tag("large-dataset"))) {
375
TestData.generateLarge()
376
} else {
377
TestData.generateSmall()
378
}
379
data
380
},
381
teardown = { data => data.cleanup() }
382
)
383
```