or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

async.mddocs/

0

# Asynchronous Testing

1

2

ScalaTest provides comprehensive support for testing asynchronous code including Futures, eventual consistency patterns, time-based assertions, and concurrent test execution. The framework offers async test suite variants, future handling utilities, retry mechanisms, and timeout controls for robust asynchronous testing.

3

4

## Capabilities

5

6

### Async Test Suites

7

8

Async variants of all test styles that return `Future[Assertion]` instead of `Assertion`, enabling proper async test execution.

9

10

```scala { .api }

11

/**

12

* Base trait for asynchronous test suites

13

*/

14

trait AsyncTestSuite extends Suite {

15

/**

16

* Implicit execution context for Future operations

17

*/

18

implicit def executionContext: ExecutionContext

19

20

/**

21

* Transform assertion Future to compatible result type

22

*/

23

final def transformToOutcome(futureAssertion: Future[compatible.Assertion]): AsyncOutcome

24

}

25

26

/**

27

* Async function-based test suite

28

*/

29

abstract class AsyncFunSuite extends AsyncTestSuite with TestSuite {

30

/**

31

* Register async test returning Future[Assertion]

32

* @param testName the name of the test

33

* @param testFun async test function returning Future[Assertion]

34

*/

35

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

36

37

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

38

}

39

40

// Similar async variants for all test styles:

41

abstract class AsyncFlatSpec extends AsyncTestSuite with TestSuite

42

abstract class AsyncWordSpec extends AsyncTestSuite with TestSuite

43

abstract class AsyncFreeSpec extends AsyncTestSuite with TestSuite

44

abstract class AsyncFunSpec extends AsyncTestSuite with TestSuite

45

abstract class AsyncFeatureSpec extends AsyncTestSuite with TestSuite

46

abstract class AsyncPropSpec extends AsyncTestSuite with TestSuite

47

```

48

49

**Usage Example:**

50

51

```scala

52

import org.scalatest.funsuite.AsyncFunSuite

53

import org.scalatest.matchers.should.Matchers

54

import scala.concurrent.Future

55

import scala.concurrent.duration._

56

57

class AsyncServiceSpec extends AsyncFunSuite with Matchers {

58

59

test("async operation should complete successfully") {

60

val service = new AsyncUserService()

61

62

// Return Future[Assertion] - ScalaTest handles async completion

63

service.createUser("John", "john@example.com").map { user =>

64

user.name should equal("John")

65

user.email should equal("john@example.com")

66

user.id should not be empty

67

}

68

}

69

70

test("async operation should handle errors") {

71

val service = new AsyncUserService()

72

73

// Test async failures

74

recoverToSucceededIf[ValidationException] {

75

service.createUser("", "invalid-email")

76

}

77

}

78

79

test("multiple async operations") {

80

val service = new AsyncUserService()

81

82

for {

83

user1 <- service.createUser("Alice", "alice@example.com")

84

user2 <- service.createUser("Bob", "bob@example.com")

85

users <- service.getUsers()

86

} yield {

87

users should contain(user1)

88

users should contain(user2)

89

users should have size 2

90

}

91

}

92

}

93

```

94

95

### Future Testing with ScalaFutures

96

97

Test Scala Futures with patience configuration and automatic waiting.

98

99

```scala { .api }

100

/**

101

* Utilities for testing Scala Futures

102

*/

103

trait ScalaFutures extends Futures with PatienceConfiguration {

104

/**

105

* Wait for future completion and apply assertions

106

* @param future the Future to wait for

107

* @param fun assertions to apply to the completed value

108

*/

109

def whenReady[T](future: Future[T])(fun: T => Unit): Unit

110

111

/**

112

* Wait for future completion with custom patience config

113

*/

114

def whenReady[T](future: Future[T], config: PatienceConfig)(fun: T => Unit): Unit

115

116

/**

117

* Implicit conversion to enable .futureValue syntax

118

*/

119

implicit def convertScalaFuture[T](future: Future[T]): FutureValue[T]

120

}

121

122

/**

123

* Enhanced Future with testing methods

124

*/

125

final class FutureValue[T](future: Future[T]) {

126

/**

127

* Block and return the future's value (with timeout)

128

*/

129

def futureValue: T

130

131

/**

132

* Block and return value with custom patience

133

*/

134

def futureValue(config: PatienceConfig): T

135

}

136

```

137

138

**Usage Examples:**

139

140

```scala

141

import org.scalatest.funsuite.AnyFunSuite

142

import org.scalatest.matchers.should.Matchers

143

import org.scalatest.concurrent.ScalaFutures

144

import org.scalatest.time.{Seconds, Span}

145

import scala.concurrent.Future

146

147

class FutureTestingSpec extends AnyFunSuite with Matchers with ScalaFutures {

148

149

test("testing futures with whenReady") {

150

val future = Future {

151

Thread.sleep(100)

152

"Hello, World!"

153

}

154

155

whenReady(future) { result =>

156

result should equal("Hello, World!")

157

result should startWith("Hello")

158

}

159

}

160

161

test("testing futures with futureValue") {

162

val future = asyncComputation()

163

164

// Block until completion and get value

165

future.futureValue should be > 0

166

future.futureValue should be <= 100

167

}

168

169

test("custom patience configuration") {

170

val slowFuture = Future {

171

Thread.sleep(2000)

172

42

173

}

174

175

// Custom timeout for slow operations

176

whenReady(slowFuture, timeout(Span(5, Seconds))) { result =>

177

result should equal(42)

178

}

179

}

180

181

test("testing future failures") {

182

val failingFuture = Future {

183

throw new RuntimeException("Something went wrong")

184

}

185

186

// Test that future fails with expected exception

187

whenReady(failingFuture.failed) { exception =>

188

exception shouldBe a[RuntimeException]

189

exception.getMessage should include("went wrong")

190

}

191

}

192

}

193

```

194

195

### Eventual Consistency with Eventually

196

197

Test systems that achieve consistency over time using retry mechanisms.

198

199

```scala { .api }

200

/**

201

* Retry assertions until they succeed or timeout

202

*/

203

trait Eventually extends PatienceConfiguration {

204

/**

205

* Retry assertion until success or timeout

206

* @param fun assertion block to retry

207

* @return successful assertion result

208

*/

209

def eventually[T](fun: => T): T

210

211

/**

212

* Eventually with custom patience configuration

213

*/

214

def eventually[T](config: PatienceConfig)(fun: => T): T

215

}

216

```

217

218

**Usage Examples:**

219

220

```scala

221

import org.scalatest.funsuite.AnyFunSuite

222

import org.scalatest.matchers.should.Matchers

223

import org.scalatest.concurrent.Eventually

224

import org.scalatest.time.{Seconds, Millis, Span}

225

226

class EventualConsistencySpec extends AnyFunSuite with Matchers with Eventually {

227

228

test("eventually consistent cache") {

229

val cache = new EventuallyConsistentCache()

230

cache.put("key", "value")

231

232

// May not be immediately available, but should be eventually

233

eventually {

234

cache.get("key") should equal(Some("value"))

235

}

236

}

237

238

test("distributed system synchronization") {

239

val cluster = new DistributedCluster()

240

cluster.addNode("node1", "data")

241

242

// Data should replicate to all nodes eventually

243

eventually(timeout(Span(10, Seconds)), interval(Span(500, Millis))) {

244

cluster.getAllNodes().foreach { node =>

245

node.getData() should contain("data")

246

}

247

}

248

}

249

250

test("UI state changes") {

251

val ui = new AsyncUI()

252

ui.startLoading()

253

254

// UI should show loading state eventually

255

eventually {

256

ui.isLoading should be(true)

257

ui.getLoadingText() should equal("Loading...")

258

}

259

260

ui.finishLoading()

261

262

// UI should stop loading eventually

263

eventually {

264

ui.isLoading should be(false)

265

}

266

}

267

}

268

```

269

270

### Time Limits and Timeouts

271

272

Impose time constraints on test execution and operations.

273

274

```scala { .api }

275

/**

276

* Impose time limits on test operations

277

*/

278

trait TimeLimits {

279

/**

280

* Fail test if operation takes longer than specified timeout

281

* @param timeout maximum allowed time

282

* @param fun operation to time-limit

283

* @return result if completed within timeout

284

*/

285

def failAfter[T](timeout: Span)(fun: => T): T

286

287

/**

288

* Cancel test if operation takes longer than timeout

289

*/

290

def cancelAfter[T](timeout: Span)(fun: => T): T

291

}

292

293

/**

294

* Automatic time limits for all tests in suite

295

*/

296

trait TimeLimitedTests extends TimeLimits {

297

/**

298

* Default timeout applied to all tests

299

*/

300

def timeLimit: Span

301

302

/**

303

* Override for specific tests that need different timeouts

304

*/

305

override def withFixture(test: NoArgTest): Outcome = {

306

failAfter(timeLimit)(super.withFixture(test))

307

}

308

}

309

310

/**

311

* Async version of time-limited tests

312

*/

313

trait AsyncTimeLimitedTests extends AsyncTestSuite with TimeLimits {

314

def timeLimit: Span

315

316

override def withFixture(test: NoArgAsyncTest): FutureOutcome = {

317

val superWithFixture = super.withFixture(test)

318

val timedFuture = failAfter(timeLimit)(superWithFixture.toFuture)

319

new FutureOutcome(timedFuture)

320

}

321

}

322

```

323

324

**Usage Examples:**

325

326

```scala

327

import org.scalatest.funsuite.AnyFunSuite

328

import org.scalatest.matchers.should.Matchers

329

import org.scalatest.concurrent.{TimeLimits, TimeLimitedTests}

330

import org.scalatest.time.{Seconds, Span}

331

332

class TimeoutSpec extends AnyFunSuite with Matchers with TimeLimits {

333

334

test("operation should complete within time limit") {

335

failAfter(Span(2, Seconds)) {

336

val result = performExpensiveCalculation()

337

result should be > 0

338

}

339

}

340

341

test("slow operation should timeout") {

342

intercept[TestFailedDueToTimeoutException] {

343

failAfter(Span(1, Seconds)) {

344

Thread.sleep(2000) // Takes longer than 1 second

345

"completed"

346

}

347

}

348

}

349

}

350

351

class AutoTimeLimitedSpec extends AnyFunSuite with Matchers with TimeLimitedTests {

352

353

// All tests in this suite automatically fail after 5 seconds

354

def timeLimit = Span(5, Seconds)

355

356

test("fast test completes normally") {

357

val result = quickOperation()

358

result should not be null

359

}

360

361

test("slow test gets automatic timeout") {

362

// This test will automatically fail after 5 seconds

363

Thread.sleep(6000) // Takes longer than timeLimit

364

}

365

}

366

```

367

368

### Patience Configuration

369

370

Configure timeout and retry behavior for async operations.

371

372

```scala { .api }

373

/**

374

* Configuration for operations that require waiting

375

*/

376

trait PatienceConfiguration {

377

/**

378

* Configuration for timeouts and retry intervals

379

*/

380

case class PatienceConfig(

381

timeout: Span, // Maximum time to wait

382

interval: Span // Time between retry attempts

383

)

384

385

/**

386

* Default patience configuration

387

*/

388

implicit def patienceConfig: PatienceConfig

389

390

/**

391

* Convenience methods for creating timeouts

392

*/

393

def timeout(value: Span): PatienceConfig

394

def interval(value: Span): PatienceConfig

395

}

396

397

/**

398

* Extended timeouts for integration testing

399

*/

400

trait IntegrationPatience extends PatienceConfiguration {

401

// Longer default timeouts suitable for integration tests

402

implicit override val patienceConfig: PatienceConfig =

403

PatienceConfig(timeout = Span(15, Seconds), interval = Span(150, Millis))

404

}

405

```

406

407

**Usage Examples:**

408

409

```scala

410

import org.scalatest.time.{Seconds, Millis, Span}

411

import org.scalatest.concurrent.{Eventually, IntegrationPatience}

412

413

class PatienceConfigSpec extends AnyFunSuite with Matchers

414

with Eventually with IntegrationPatience {

415

416

test("custom patience for specific operation") {

417

val service = new SlowExternalService()

418

419

// Custom patience for this specific test

420

eventually(timeout(Span(30, Seconds)), interval(Span(1, Seconds))) {

421

service.isHealthy() should be(true)

422

}

423

}

424

425

test("integration test with extended patience") {

426

// Uses IntegrationPatience defaults (15 seconds timeout)

427

val database = new DatabaseConnection()

428

429

eventually {

430

database.isConnected() should be(true)

431

database.getStatus() should equal("ready")

432

}

433

}

434

}

435

```

436

437

### Concurrent Test Coordination

438

439

Coordinate multiple threads and test concurrent behavior.

440

441

```scala { .api }

442

/**

443

* Coordinate multi-threaded test scenarios

444

*/

445

trait Conductors {

446

/**

447

* Create a conductor for orchestrating concurrent test execution

448

*/

449

def conductor: Conductor

450

}

451

452

/**

453

* Conductor for multi-threaded test coordination

454

*/

455

class Conductor {

456

/**

457

* Execute function in a separate thread

458

* @param fun function to execute concurrently

459

*/

460

def thread[T](fun: => T): Thread

461

462

/**

463

* Wait for all threads to complete

464

*/

465

def whenFinished(fun: => Unit): Unit

466

}

467

468

/**

469

* Thread synchronization utilities

470

*/

471

trait Waiters {

472

/**

473

* Create a waiter for thread coordination

474

*/

475

def waiter(): Waiter

476

}

477

478

class Waiter {

479

/**

480

* Signal that an expected event occurred

481

*/

482

def dismiss(): Unit

483

484

/**

485

* Wait for expected events with timeout

486

*/

487

def await(timeout: Span = Span(150, Millis)): Unit

488

}

489

```

490

491

**Usage Examples:**

492

493

```scala

494

import org.scalatest.funsuite.AnyFunSuite

495

import org.scalatest.matchers.should.Matchers

496

import org.scalatest.concurrent.{Conductors, Waiters}

497

import java.util.concurrent.atomic.AtomicInteger

498

499

class ConcurrentTestSpec extends AnyFunSuite with Matchers

500

with Conductors with Waiters {

501

502

test("concurrent counter increments") {

503

val counter = new AtomicInteger(0)

504

val conductor = this.conductor

505

506

conductor.thread {

507

for (i <- 1 to 100) {

508

counter.incrementAndGet()

509

}

510

}

511

512

conductor.thread {

513

for (i <- 1 to 100) {

514

counter.incrementAndGet()

515

}

516

}

517

518

conductor.whenFinished {

519

counter.get() should equal(200)

520

}

521

}

522

523

test("producer-consumer coordination") {

524

val buffer = new java.util.concurrent.ArrayBlockingQueue[String](10)

525

val waiter = this.waiter()

526

527

val producer = new Thread {

528

override def run(): Unit = {

529

buffer.put("item1")

530

buffer.put("item2")

531

waiter.dismiss() // Signal items are available

532

}

533

}

534

535

val consumer = new Thread {

536

override def run(): Unit = {

537

waiter.await() // Wait for items

538

val item1 = buffer.take()

539

val item2 = buffer.take()

540

item1 should equal("item1")

541

item2 should equal("item2")

542

}

543

}

544

545

producer.start()

546

consumer.start()

547

548

producer.join()

549

consumer.join()

550

}

551

}

552

```

553

554

## Common Async Testing Patterns

555

556

### Testing Async Services

557

558

```scala

559

test("async service integration") {

560

val service = new AsyncEmailService()

561

562

for {

563

result <- service.sendEmail("user@example.com", "Hello", "Test message")

564

status <- service.getDeliveryStatus(result.messageId)

565

} yield {

566

result.success should be(true)

567

status should equal("delivered")

568

}

569

}

570

```

571

572

### Error Handling in Async Tests

573

574

```scala

575

test("async error handling") {

576

val service = new AsyncUserService()

577

578

// Test successful recovery

579

recoverToSucceededIf[ValidationException] {

580

service.createUser("", "invalid-email")

581

}

582

583

// Test specific error details

584

service.createUser("", "").failed.map { exception =>

585

exception shouldBe a[ValidationException]

586

exception.getMessage should include("name")

587

}

588

}

589

```

590

591

### Testing Event Streams

592

593

```scala

594

test("event stream processing") {

595

val eventStream = new AsyncEventStream()

596

eventStream.start()

597

598

eventually {

599

eventStream.getProcessedCount() should be > 0

600

}

601

602

eventStream.stop()

603

604

eventually {

605

eventStream.isRunning() should be(false)

606

}

607

}

608

```

609

610

### Parallel Test Execution

611

612

```scala

613

// Enable parallel execution for the entire suite

614

class ParallelTestSuite extends AnyFunSuite with ParallelTestExecution {

615

// Tests in this suite run in parallel by default

616

617

test("independent test 1") {

618

// This can run concurrently with other tests

619

performIndependentOperation()

620

}

621

622

test("independent test 2") {

623

// This can also run concurrently

624

performAnotherIndependentOperation()

625

}

626

}

627

```