0
# Async Testing
1
2
ScalaTest provides comprehensive support for asynchronous testing with Future-based tests, automatic timeout handling, and integration with concurrent utilities. All test styles have async variants that return `Future[Assertion]` instead of `Assertion`.
3
4
## Capabilities
5
6
### Async Test Suites
7
8
All ScalaTest styles have async variants for testing asynchronous code.
9
10
```scala { .api }
11
import org.scalatest.funsuite.AsyncFunSuite
12
import org.scalatest.flatspec.AsyncFlatSpec
13
import org.scalatest.wordspec.AsyncWordSpec
14
import org.scalatest.freespec.AsyncFreeSpec
15
import org.scalatest.featurespec.AsyncFeatureSpec
16
import org.scalatest.funspec.AsyncFunSpec
17
import org.scalatest.propspec.AsyncPropSpec
18
19
// Base async test suite trait
20
trait AsyncTestSuite extends Suite with RecoverMethods with CompleteLastly {
21
// Test methods return Future[Assertion] instead of Assertion
22
protected def transformToOutcome(testFun: => Future[compatible.Assertion]): () => AsyncOutcome
23
24
// Execution context for running async tests
25
implicit def executionContext: ExecutionContext
26
27
// Timeout configuration
28
implicit val patienceConfig: PatienceConfig
29
}
30
31
// Async FunSuite example
32
abstract class AsyncFunSuite extends AsyncTestSuite with AsyncTestSuiteMixin {
33
protected final class AsyncFunSuiteAsyncTest(testName: String, testTags: Tag*)
34
extends AsyncTest(testName, testTags: _*)
35
36
def test(testName: String, testTags: Tag*)(testFun: => Future[compatible.Assertion]): Unit
37
def ignore(testName: String, testTags: Tag*)(testFun: => Future[compatible.Assertion]): Unit
38
}
39
```
40
41
**Async Test Examples:**
42
```scala
43
import org.scalatest.funsuite.AsyncFunSuite
44
import scala.concurrent.Future
45
import scala.concurrent.duration._
46
47
class AsyncCalculatorSuite extends AsyncFunSuite {
48
test("async addition should work") {
49
val futureResult = Future {
50
Thread.sleep(100) // Simulate async work
51
2 + 2
52
}
53
54
futureResult.map { result =>
55
assert(result === 4)
56
}
57
}
58
59
test("multiple async operations") {
60
val future1 = Future(10)
61
val future2 = Future(20)
62
63
for {
64
a <- future1
65
b <- future2
66
} yield {
67
assert(a + b === 30)
68
}
69
}
70
71
test("async failure handling") {
72
val failingFuture = Future.failed[Int](new RuntimeException("Expected failure"))
73
74
recoverToSucceededIf[RuntimeException] {
75
failingFuture
76
}
77
}
78
}
79
```
80
81
### Future Assertions
82
83
Specialized assertion methods for Future-based testing.
84
85
```scala { .api }
86
import org.scalatest.concurrent.ScalaFutures
87
88
trait ScalaFutures {
89
// Configuration for Future handling
90
implicit val patienceConfig: PatienceConfig
91
92
// Block until Future completes and apply function to result
93
def whenReady[T](future: Future[T], timeout: Timeout = timeout, interval: Interval = interval)
94
(fun: T => Unit): Unit
95
96
// Extract Future value (blocking)
97
implicit class FutureValues[T](future: Future[T]) {
98
def futureValue: T
99
def futureValue(timeout: Timeout): T
100
}
101
102
// Assert Future completes successfully
103
def noException should be thrownBy future
104
105
// Assert Future fails with specific exception
106
a [ExceptionType] should be thrownBy future
107
}
108
109
// Patience configuration
110
case class PatienceConfig(timeout: Span, interval: Span)
111
object PatienceConfig {
112
implicit val defaultPatienceConfig: PatienceConfig =
113
PatienceConfig(timeout = scaled(Span(150, Millis)), interval = scaled(Span(15, Millis)))
114
}
115
```
116
117
**Future Assertion Examples:**
118
```scala
119
import org.scalatest.flatspec.AnyFlatSpec
120
import org.scalatest.concurrent.ScalaFutures
121
import org.scalatest.matchers.should.Matchers
122
import scala.concurrent.Future
123
import scala.concurrent.duration._
124
125
class FutureAssertionSpec extends AnyFlatSpec with Matchers with ScalaFutures {
126
implicit override val patienceConfig = PatienceConfig(
127
timeout = scaled(Span(5, Seconds)),
128
interval = scaled(Span(50, Millis))
129
)
130
131
"Future assertions" should "work with whenReady" in {
132
val future = Future { 42 }
133
134
whenReady(future) { result =>
135
result should be(42)
136
}
137
}
138
139
"Future values" should "be extractable" in {
140
val future = Future { "hello world" }
141
142
future.futureValue should startWith("hello")
143
future.futureValue should have length 11
144
}
145
146
"Failed futures" should "be testable" in {
147
val failedFuture = Future.failed[String](new IllegalArgumentException("Bad argument"))
148
149
a [IllegalArgumentException] should be thrownBy {
150
failedFuture.futureValue
151
}
152
}
153
}
154
```
155
156
### Async Recovery Methods
157
158
Handle and test for expected failures in async code.
159
160
```scala { .api }
161
trait RecoverMethods {
162
// Recover from expected exception type
163
def recoverToSucceededIf[T <: AnyRef](future: Future[Any])
164
(implicit classTag: ClassTag[T]): Future[Assertion]
165
166
// Recover and return the exception
167
def recoverToExceptionIf[T <: AnyRef](future: Future[Any])
168
(implicit classTag: ClassTag[T]): Future[T]
169
}
170
171
// Usage in async tests
172
class AsyncRecoverySpec extends AsyncFunSuite {
173
test("should recover from expected exception") {
174
val failingFuture = Future.failed[Int](new IllegalArgumentException("Expected"))
175
176
recoverToSucceededIf[IllegalArgumentException] {
177
failingFuture
178
}
179
}
180
181
test("should capture and examine exception") {
182
val failingFuture = Future.failed[Int](new IllegalArgumentException("Test message"))
183
184
recoverToExceptionIf[IllegalArgumentException] {
185
failingFuture
186
}.map { exception =>
187
assert(exception.getMessage === "Test message")
188
}
189
}
190
}
191
```
192
193
### Async Fixture Support
194
195
Manage asynchronous test fixtures and resources.
196
197
```scala { .api }
198
import org.scalatest.funsuite.FixtureAsyncFunSuite
199
200
// Async fixture suite
201
abstract class FixtureAsyncFunSuite extends FixtureAsyncTestSuite with AsyncTestSuiteMixin {
202
type FixtureParam
203
204
// Async fixture management
205
def withFixture(test: OneArgAsyncTest): FutureOutcome
206
207
// Async test definition
208
def test(testName: String, testTags: Tag*)(testFun: FixtureParam => Future[compatible.Assertion]): Unit
209
}
210
211
// Async fixture example
212
class AsyncDatabaseSuite extends FixtureAsyncFunSuite {
213
type FixtureParam = Database
214
215
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
216
val database = Database.connect()
217
complete {
218
super.withFixture(test.toNoArgAsyncTest(database))
219
} lastly {
220
database.close()
221
}
222
}
223
224
test("async database operations") { db =>
225
for {
226
_ <- db.insert("user", Map("name" -> "Alice"))
227
user <- db.findByName("Alice")
228
} yield {
229
assert(user.name === "Alice")
230
}
231
}
232
}
233
```
234
235
### Eventually and Async Patience
236
237
Test conditions that should eventually become true.
238
239
```scala { .api }
240
import org.scalatest.concurrent.Eventually
241
242
trait Eventually {
243
// Repeatedly test condition until it succeeds or times out
244
def eventually[T](fun: => T)(implicit config: PatienceConfig): T
245
246
// Configuration for eventually
247
implicit val patienceConfig: PatienceConfig
248
}
249
250
// Integration with async tests
251
trait AsyncEventually extends Eventually {
252
def eventually[T](fun: => Future[T])(implicit config: PatienceConfig): Future[T]
253
}
254
```
255
256
**Eventually Examples:**
257
```scala
258
import org.scalatest.concurrent.Eventually
259
import org.scalatest.time.{Millis, Seconds, Span}
260
261
class EventuallySpec extends AnyFlatSpec with Eventually {
262
implicit override val patienceConfig = PatienceConfig(
263
timeout = scaled(Span(5, Seconds)),
264
interval = scaled(Span(100, Millis))
265
)
266
267
"Eventually" should "wait for condition to become true" in {
268
var counter = 0
269
270
eventually {
271
counter += 1
272
assert(counter >= 10)
273
}
274
}
275
276
"Eventually with async" should "work with futures" in {
277
@volatile var ready = false
278
279
// Simulate async operation that sets ready flag
280
Future {
281
Thread.sleep(1000)
282
ready = true
283
}
284
285
eventually {
286
assert(ready === true)
287
}
288
}
289
}
290
```
291
292
### Async Test Execution Context
293
294
Configure execution context for async tests.
295
296
```scala { .api }
297
// Default execution context
298
import org.scalatest.concurrent.ScalaFutures._
299
300
trait AsyncTestSuite {
301
// Default execution context (can be overridden)
302
implicit def executionContext: ExecutionContext =
303
scala.concurrent.ExecutionContext.Implicits.global
304
}
305
306
// Custom execution context
307
class CustomAsyncSuite extends AsyncFunSuite {
308
// Use custom thread pool
309
implicit override def executionContext: ExecutionContext =
310
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(4))
311
}
312
```
313
314
### Async Timeouts and Configuration
315
316
Configure timeouts and patience parameters for async operations.
317
318
```scala { .api }
319
import org.scalatest.time._
320
321
// Time span specifications
322
case class Span(length: Long, unit: Units)
323
324
// Timeout and interval configuration
325
case class PatienceConfig(timeout: Span, interval: Span) {
326
def scaled(factor: Double): PatienceConfig
327
}
328
329
// Predefined time units
330
object Span {
331
def apply(length: Long, unit: Units): Span
332
}
333
334
sealed abstract class Units
335
case object Nanosecond extends Units
336
case object Nanoseconds extends Units
337
case object Microsecond extends Units
338
case object Microseconds extends Units
339
case object Millisecond extends Units
340
case object Milliseconds extends Units
341
case object Millis extends Units
342
case object Second extends Units
343
case object Seconds extends Units
344
case object Minute extends Units
345
case object Minutes extends Units
346
case object Hour extends Units
347
case object Hours extends Units
348
case object Day extends Units
349
case object Days extends Units
350
```
351
352
**Timeout Configuration Examples:**
353
```scala
354
import org.scalatest.time._
355
import scala.concurrent.duration._
356
357
class TimeoutConfigSpec extends AsyncFunSuite {
358
// Custom patience configuration
359
implicit override val patienceConfig = PatienceConfig(
360
timeout = scaled(Span(10, Seconds)),
361
interval = scaled(Span(200, Millis))
362
)
363
364
test("long running operation") {
365
val longFuture = Future {
366
Thread.sleep(2000) // 2 second delay
367
"completed"
368
}
369
370
longFuture.map { result =>
371
assert(result === "completed")
372
}
373
}
374
375
test("operation with custom timeout") {
376
val future = Future { "result" }
377
378
whenReady(future, timeout(5.seconds)) { result =>
379
assert(result === "result")
380
}
381
}
382
}
383
```
384
385
### Async Parallel Testing
386
387
Run async tests in parallel for better performance.
388
389
```scala { .api }
390
// Parallel async execution
391
trait ParallelTestExecution { this: AsyncTestSuite =>
392
// Enable parallel execution of async tests
393
}
394
395
class ParallelAsyncSuite extends AsyncFunSuite with ParallelTestExecution {
396
test("parallel async test 1") {
397
Future {
398
Thread.sleep(100)
399
assert(1 + 1 === 2)
400
}
401
}
402
403
test("parallel async test 2") {
404
Future {
405
Thread.sleep(100)
406
assert(2 + 2 === 4)
407
}
408
}
409
}
410
```
411
412
### Integration with Reactive Streams
413
414
Test reactive streams and async data processing.
415
416
```scala { .api }
417
// Example with Akka Streams (not part of ScalaTest core)
418
import akka.stream.scaladsl.Source
419
import akka.stream.testkit.scaladsl.TestSink
420
421
class StreamTestSpec extends AsyncFunSuite {
422
test("stream processing") {
423
val source = Source(1 to 10)
424
.map(_ * 2)
425
.filter(_ > 10)
426
427
val future = source.runFold(0)(_ + _)
428
429
future.map { sum =>
430
assert(sum === 60) // 12 + 14 + 16 + 18 + 20 = 80
431
}
432
}
433
}
434
```
435
436
### Async Error Handling Patterns
437
438
Common patterns for handling errors in async tests.
439
440
```scala
441
class AsyncErrorHandlingSpec extends AsyncFunSuite {
442
test("transform failures") {
443
val future = Future.failed[Int](new RuntimeException("Original error"))
444
445
future.recover {
446
case _: RuntimeException => 42
447
}.map { result =>
448
assert(result === 42)
449
}
450
}
451
452
test("chain operations with error handling") {
453
def mightFail(value: Int): Future[Int] = {
454
if (value < 0) Future.failed(new IllegalArgumentException("Negative value"))
455
else Future.successful(value * 2)
456
}
457
458
val result = for {
459
a <- Future.successful(5)
460
b <- mightFail(a)
461
c <- mightFail(b)
462
} yield c
463
464
result.map { finalValue =>
465
assert(finalValue === 20) // 5 * 2 * 2
466
}
467
}
468
469
test("verify specific failure") {
470
val future = Future.failed[String](new IllegalStateException("Bad state"))
471
472
recoverToSucceededIf[IllegalStateException] {
473
future
474
}
475
}
476
}