or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced.mdassertions.mdasync.mdfixtures.mdindex.mdmatchers.mdtest-styles.md

fixtures.mddocs/

0

# Fixtures and Lifecycle Management

1

2

ScalaTest provides multiple patterns for managing test setup, teardown, and shared resources across test executions. The framework offers simple before/after hooks, parameterized fixtures, loan patterns, and sophisticated lifecycle management to ensure clean, isolated, and efficient test execution.

3

4

## Capabilities

5

6

### Basic Lifecycle Hooks

7

8

Simple setup and teardown hooks that execute around each test or entire test suite.

9

10

```scala { .api }

11

/**

12

* Simple before/after hooks for each test

13

*/

14

trait BeforeAndAfter {

15

/**

16

* Execute before each test method

17

*/

18

protected def before(): Unit

19

20

/**

21

* Execute after each test method (even if test fails)

22

*/

23

protected def after(): Unit

24

}

25

26

/**

27

* Enhanced per-test lifecycle hooks

28

*/

29

trait BeforeAndAfterEach {

30

/**

31

* Execute before each test with access to test data

32

*/

33

protected def beforeEach(): Unit

34

35

/**

36

* Execute after each test with access to test data

37

*/

38

protected def afterEach(): Unit

39

}

40

41

/**

42

* Per-test hooks with test metadata access

43

*/

44

trait BeforeAndAfterEachTestData {

45

/**

46

* Execute before each test with test information

47

* @param testData metadata about the test being executed

48

*/

49

protected def beforeEach(testData: TestData): Unit

50

51

/**

52

* Execute after each test with test information

53

* @param testData metadata about the test that was executed

54

*/

55

protected def afterEach(testData: TestData): Unit

56

}

57

```

58

59

**Usage Examples:**

60

61

```scala

62

import org.scalatest.funsuite.AnyFunSuite

63

import org.scalatest.matchers.should.Matchers

64

import org.scalatest.{BeforeAndAfter, BeforeAndAfterEach}

65

66

class DatabaseTestSpec extends AnyFunSuite with Matchers with BeforeAndAfter {

67

68

var database: TestDatabase = _

69

70

before {

71

// Setup before each test

72

database = new TestDatabase()

73

database.connect()

74

database.createTestTables()

75

}

76

77

after {

78

// Cleanup after each test

79

database.dropTestTables()

80

database.disconnect()

81

}

82

83

test("user creation should work") {

84

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

85

database.save(user)

86

87

val retrieved = database.findByEmail("alice@example.com")

88

retrieved should be(defined)

89

retrieved.get.name should equal("Alice")

90

}

91

92

test("user deletion should work") {

93

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

94

database.save(user)

95

database.delete(user.id)

96

97

database.findByEmail("bob@example.com") should be(empty)

98

}

99

}

100

101

class LoggingTestSpec extends AnyFunSuite with BeforeAndAfterEach {

102

103

override def beforeEach(): Unit = {

104

println(s"Starting test: ${getClass.getSimpleName}")

105

TestLogger.setLevel(LogLevel.DEBUG)

106

}

107

108

override def afterEach(): Unit = {

109

TestLogger.clearLogs()

110

println(s"Finished test: ${getClass.getSimpleName}")

111

}

112

113

test("service should log operations") {

114

val service = new UserService()

115

service.createUser("Test User")

116

117

TestLogger.getMessages() should contain("Creating user: Test User")

118

}

119

}

120

```

121

122

### Suite-Level Lifecycle Hooks

123

124

Setup and teardown that execute once per test suite rather than per test.

125

126

```scala { .api }

127

/**

128

* Suite-level lifecycle hooks

129

*/

130

trait BeforeAndAfterAll {

131

/**

132

* Execute once before all tests in the suite

133

*/

134

protected def beforeAll(): Unit

135

136

/**

137

* Execute once after all tests in the suite (even if tests fail)

138

*/

139

protected def afterAll(): Unit

140

}

141

142

/**

143

* Suite-level hooks with configuration access

144

*/

145

trait BeforeAndAfterAllConfigMap {

146

/**

147

* Execute before all tests with access to configuration

148

* @param configMap configuration passed to the test run

149

*/

150

protected def beforeAll(configMap: ConfigMap): Unit

151

152

/**

153

* Execute after all tests with access to configuration

154

* @param configMap configuration passed to the test run

155

*/

156

protected def afterAll(configMap: ConfigMap): Unit

157

}

158

```

159

160

**Usage Examples:**

161

162

```scala

163

import org.scalatest.funsuite.AnyFunSuite

164

import org.scalatest.matchers.should.Matchers

165

import org.scalatest.BeforeAndAfterAll

166

167

class IntegrationTestSpec extends AnyFunSuite with Matchers with BeforeAndAfterAll {

168

169

var server: TestServer = _

170

var database: TestDatabase = _

171

172

override def beforeAll(): Unit = {

173

// Expensive setup once per suite

174

database = new TestDatabase()

175

database.migrate()

176

177

server = new TestServer(database)

178

server.start()

179

180

println("Test environment ready")

181

}

182

183

override def afterAll(): Unit = {

184

// Cleanup once per suite

185

try {

186

server.stop()

187

database.cleanup()

188

} catch {

189

case e: Exception => println(s"Cleanup error: ${e.getMessage}")

190

}

191

}

192

193

test("API should create users") {

194

val response = server.post("/users", """{"name": "Alice"}""")

195

response.status should equal(201)

196

response.body should include("Alice")

197

}

198

199

test("API should list users") {

200

server.post("/users", """{"name": "Bob"}""")

201

val response = server.get("/users")

202

203

response.status should equal(200)

204

response.body should include("Bob")

205

}

206

}

207

```

208

209

### Parameterized Fixtures

210

211

Pass specific fixture objects to each test method using the fixture traits.

212

213

```scala { .api }

214

/**

215

* Base for fixture-based test suites

216

*/

217

trait fixture.TestSuite extends Suite {

218

/**

219

* The type of fixture object passed to tests

220

*/

221

type FixtureParam

222

223

/**

224

* Create and potentially cleanup fixture for each test

225

* @param test the test function that receives the fixture

226

*/

227

def withFixture(test: OneArgTest): Outcome

228

}

229

230

/**

231

* Fixture variants for all test styles

232

*/

233

abstract class fixture.FunSuite extends fixture.TestSuite {

234

/**

235

* Register test that receives fixture parameter

236

* @param testName name of the test

237

* @param testFun test function receiving fixture

238

*/

239

protected def test(testName: String)(testFun: FixtureParam => Any): Unit

240

}

241

242

// Similar fixture variants available:

243

// fixture.FlatSpec, fixture.WordSpec, fixture.FreeSpec,

244

// fixture.FunSpec, fixture.FeatureSpec, fixture.PropSpec

245

```

246

247

**Usage Examples:**

248

249

```scala

250

import org.scalatest.fixture

251

import org.scalatest.matchers.should.Matchers

252

import org.scalatest.Outcome

253

254

class FixtureExampleSpec extends fixture.FunSuite with Matchers {

255

256

// Define the fixture type

257

type FixtureParam = TestDatabase

258

259

// Create fixture for each test

260

def withFixture(test: OneArgTest): Outcome = {

261

val database = new TestDatabase()

262

database.connect()

263

database.createTestTables()

264

265

try {

266

test(database) // Pass fixture to test

267

} finally {

268

database.dropTestTables()

269

database.disconnect()

270

}

271

}

272

273

test("user operations with database fixture") { db =>

274

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

275

db.save(user)

276

277

val retrieved = db.findByEmail("alice@example.com")

278

retrieved should be(defined)

279

retrieved.get.name should equal("Alice")

280

}

281

282

test("concurrent user access") { db =>

283

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

284

val user2 = User("Charlie", "charlie@example.com")

285

286

db.save(user1)

287

db.save(user2)

288

289

db.count() should equal(2)

290

}

291

}

292

```

293

294

### Fixture Composition and Stackable Traits

295

296

Combine multiple fixture patterns using stackable traits.

297

298

```scala { .api }

299

/**

300

* Stack multiple fixture traits together

301

*/

302

trait fixture.TestSuiteMixin extends fixture.TestSuite {

303

// Stackable fixture behavior

304

abstract override def withFixture(test: OneArgTest): Outcome

305

}

306

```

307

308

**Usage Examples:**

309

310

```scala

311

import org.scalatest.fixture

312

import org.scalatest.matchers.should.Matchers

313

import org.scalatest.{Outcome, BeforeAndAfterAll}

314

315

// Stackable database fixture

316

trait DatabaseFixture extends fixture.TestSuiteMixin {

317

this: fixture.TestSuite =>

318

319

type FixtureParam = TestDatabase

320

321

abstract override def withFixture(test: OneArgTest): Outcome = {

322

val database = new TestDatabase()

323

database.connect()

324

325

try {

326

super.withFixture(test.toNoArgTest(database))

327

} finally {

328

database.disconnect()

329

}

330

}

331

}

332

333

// Stackable HTTP client fixture

334

trait HttpClientFixture extends fixture.TestSuiteMixin {

335

this: fixture.TestSuite =>

336

337

abstract override def withFixture(test: OneArgTest): Outcome = {

338

val httpClient = new TestHttpClient()

339

httpClient.configure()

340

341

try {

342

super.withFixture(test)

343

} finally {

344

httpClient.cleanup()

345

}

346

}

347

}

348

349

class ComposedFixtureSpec extends fixture.FunSuite

350

with Matchers with BeforeAndAfterAll

351

with DatabaseFixture with HttpClientFixture {

352

353

test("integration test with multiple fixtures") { db =>

354

// Both database and HTTP client are available

355

val user = User("Integration", "integration@test.com")

356

db.save(user)

357

358

// HTTP client configured by HttpClientFixture

359

val response = httpClient.get(s"/users/${user.id}")

360

response.status should equal(200)

361

}

362

}

363

```

364

365

### Loan Pattern Fixtures

366

367

Use the loan pattern for automatic resource management with try-finally semantics.

368

369

**Usage Examples:**

370

371

```scala

372

import org.scalatest.funsuite.AnyFunSuite

373

import org.scalatest.matchers.should.Matchers

374

375

class LoanPatternSpec extends AnyFunSuite with Matchers {

376

377

// Loan pattern helper

378

def withTempFile[T](content: String)(testCode: java.io.File => T): T = {

379

val tempFile = java.io.File.createTempFile("test", ".txt")

380

java.nio.file.Files.write(tempFile.toPath, content.getBytes)

381

382

try {

383

testCode(tempFile) // Loan the resource

384

} finally {

385

tempFile.delete() // Always cleanup

386

}

387

}

388

389

def withDatabase[T](testCode: TestDatabase => T): T = {

390

val db = new TestDatabase()

391

db.connect()

392

db.createTestTables()

393

394

try {

395

testCode(db)

396

} finally {

397

db.dropTestTables()

398

db.disconnect()

399

}

400

}

401

402

test("file processing with loan pattern") {

403

withTempFile("Hello, World!") { file =>

404

val content = scala.io.Source.fromFile(file).mkString

405

content should equal("Hello, World!")

406

file.exists() should be(true)

407

}

408

// File automatically deleted after test

409

}

410

411

test("database operations with loan pattern") {

412

withDatabase { db =>

413

val user = User("Loan", "loan@example.com")

414

db.save(user)

415

416

db.findAll() should have size 1

417

db.findByEmail("loan@example.com") should be(defined)

418

}

419

// Database automatically cleaned up

420

}

421

422

test("nested loan patterns") {

423

withDatabase { db =>

424

withTempFile("config data") { configFile =>

425

// Both resources available within nested scope

426

val config = loadConfig(configFile)

427

db.updateConfig(config)

428

429

db.getConfig() should equal(config)

430

}

431

// File cleaned up, database still available

432

db.getConfig() should not be null

433

}

434

// Database cleaned up

435

}

436

}

437

```

438

439

### One Instance Per Test

440

441

Create a new test class instance for each test method, ensuring complete isolation.

442

443

```scala { .api }

444

/**

445

* Create new instance of test class for each test method

446

*/

447

trait OneInstancePerTest extends Suite {

448

// Each test gets a fresh instance of the test class

449

// Useful for mutable fixture state

450

}

451

```

452

453

**Usage Examples:**

454

455

```scala

456

import org.scalatest.funsuite.AnyFunSuite

457

import org.scalatest.matchers.should.Matchers

458

import org.scalatest.OneInstancePerTest

459

460

class IsolatedStateSpec extends AnyFunSuite with Matchers with OneInstancePerTest {

461

462

// Mutable state - each test gets a fresh instance

463

var counter = 0

464

val cache = scala.collection.mutable.Map[String, String]()

465

466

test("first test modifies state") {

467

counter = 42

468

cache("key1") = "value1"

469

470

counter should equal(42)

471

cache should have size 1

472

}

473

474

test("second test sees fresh state") {

475

// Fresh instance - counter is 0, cache is empty

476

counter should equal(0)

477

cache should be(empty)

478

479

counter = 100

480

cache("key2") = "value2"

481

}

482

483

test("third test also sees fresh state") {

484

// Another fresh instance

485

counter should equal(0)

486

cache should be(empty)

487

}

488

}

489

```

490

491

### Test Data and Configuration

492

493

Access test metadata and configuration in lifecycle hooks.

494

495

```scala { .api }

496

/**

497

* Test metadata available in lifecycle hooks

498

*/

499

case class TestData(

500

name: String, // Test name

501

configMap: ConfigMap, // Configuration passed to test run

502

tags: Set[String] // Tags applied to the test

503

)

504

505

/**

506

* Configuration map for test runs

507

*/

508

type ConfigMap = Map[String, Any]

509

```

510

511

**Usage Examples:**

512

513

```scala

514

import org.scalatest.funsuite.AnyFunSuite

515

import org.scalatest.matchers.should.Matchers

516

import org.scalatest.{BeforeAndAfterEachTestData, TestData, Tag}

517

518

object SlowTest extends Tag("slow")

519

object DatabaseTest extends Tag("database")

520

521

class TestDataExampleSpec extends AnyFunSuite with Matchers with BeforeAndAfterEachTestData {

522

523

override def beforeEach(testData: TestData): Unit = {

524

println(s"Starting test: ${testData.name}")

525

526

if (testData.tags.contains(SlowTest.name)) {

527

println("This is a slow test - setting longer timeout")

528

}

529

530

if (testData.tags.contains(DatabaseTest.name)) {

531

println("This test needs database - ensuring connection")

532

}

533

534

// Access configuration

535

val environment = testData.configMap.getOrElse("env", "test")

536

println(s"Running in environment: $environment")

537

}

538

539

override def afterEach(testData: TestData): Unit = {

540

println(s"Finished test: ${testData.name}")

541

542

if (testData.tags.contains(DatabaseTest.name)) {

543

println("Cleaning up database resources")

544

}

545

}

546

547

test("fast unit test") {

548

// Regular test

549

1 + 1 should equal(2)

550

}

551

552

test("slow integration test", SlowTest) {

553

// Tagged as slow test

554

Thread.sleep(100)

555

"slow operation" should not be empty

556

}

557

558

test("database test", DatabaseTest) {

559

// Tagged as database test

560

"database operation" should not be empty

561

}

562

563

test("complex test", SlowTest, DatabaseTest) {

564

// Multiple tags

565

"complex operation" should not be empty

566

}

567

}

568

```

569

570

### Async Fixture Support

571

572

Fixtures for asynchronous test suites that return `Future[Assertion]`.

573

574

```scala { .api }

575

/**

576

* Async fixture support

577

*/

578

abstract class fixture.AsyncFunSuite extends fixture.AsyncTestSuite {

579

protected def test(testName: String)(testFun: FixtureParam => Future[compatible.Assertion]): Unit

580

}

581

582

// Available for all async test styles:

583

// fixture.AsyncFlatSpec, fixture.AsyncWordSpec, etc.

584

```

585

586

**Usage Examples:**

587

588

```scala

589

import org.scalatest.fixture

590

import org.scalatest.matchers.should.Matchers

591

import scala.concurrent.{Future, ExecutionContext}

592

593

class AsyncFixtureSpec extends fixture.AsyncFunSuite with Matchers {

594

595

type FixtureParam = AsyncTestService

596

597

def withFixture(test: OneArgAsyncTest): FutureOutcome = {

598

val service = new AsyncTestService()

599

600

// Setup

601

val setupFuture = service.initialize()

602

603

// Run test with cleanup

604

val testFuture = setupFuture.flatMap { _ =>

605

val testResult = test(service)

606

testResult.toFuture.andThen {

607

case _ => service.cleanup() // Cleanup regardless of result

608

}

609

}

610

611

new FutureOutcome(testFuture)

612

}

613

614

test("async service operation") { service =>

615

for {

616

result <- service.processData("test data")

617

status <- service.getStatus()

618

} yield {

619

result should not be empty

620

status should equal("processing_complete")

621

}

622

}

623

}

624

```

625

626

## Common Patterns

627

628

### Database Testing Pattern

629

630

```scala

631

trait DatabaseTestSupport extends BeforeAndAfterEach {

632

this: Suite =>

633

634

var db: TestDatabase = _

635

636

override def beforeEach(): Unit = {

637

super.beforeEach()

638

db = TestDatabase.create()

639

db.runMigrations()

640

}

641

642

override def afterEach(): Unit = {

643

try {

644

db.cleanup()

645

} finally {

646

super.afterEach()

647

}

648

}

649

}

650

```

651

652

### External Service Testing

653

654

```scala

655

trait ExternalServiceFixture extends BeforeAndAfterAll {

656

this: Suite =>

657

658

var mockServer: MockWebServer = _

659

660

override def beforeAll(): Unit = {

661

super.beforeAll()

662

mockServer = new MockWebServer()

663

mockServer.start()

664

}

665

666

override def afterAll(): Unit = {

667

try {

668

mockServer.shutdown()

669

} finally {

670

super.afterAll()

671

}

672

}

673

}

674

```

675

676

### Configuration-Based Fixtures

677

678

```scala

679

class ConfigurableTestSpec extends AnyFunSuite with BeforeAndAfterAllConfigMap {

680

681

var testConfig: TestConfig = _

682

683

override def beforeAll(configMap: ConfigMap): Unit = {

684

val environment = configMap.getOrElse("env", "test").toString

685

val dbUrl = configMap.getOrElse("db.url", "jdbc:h2:mem:test").toString

686

687

testConfig = TestConfig(environment, dbUrl)

688

}

689

690

test("configuration-driven test") {

691

testConfig.environment should not be empty

692

testConfig.databaseUrl should startWith("jdbc:")

693

}

694

}

695

```