0
# Utility Classes and Configuration
1
2
Akka TestKit provides various utility classes for configuration management, networking, serialization, and timing that support the core testing functionality. These utilities help create comprehensive test environments and handle common testing scenarios.
3
4
## Configuration System
5
6
### TestKitExtension { .api }
7
8
```scala
9
object TestKitExtension extends ExtensionId[TestKitSettings] {
10
def get(system: ActorSystem): TestKitSettings
11
def get(system: ClassicActorSystemProvider): TestKitSettings
12
def createExtension(system: ExtendedActorSystem): TestKitSettings
13
}
14
```
15
16
Extension system for accessing TestKit configuration settings.
17
18
### TestKitSettings { .api }
19
20
```scala
21
class TestKitSettings(config: Config) {
22
val TestTimeFactor: Double
23
val SingleExpectDefaultTimeout: FiniteDuration
24
val TestEventFilterLeeway: FiniteDuration
25
val DefaultTimeout: Timeout
26
}
27
```
28
29
Configuration holder containing all TestKit-related settings loaded from configuration files.
30
31
### Usage Examples
32
33
#### Accessing TestKit Settings
34
35
```scala
36
import akka.testkit.{TestKitExtension, TestKitSettings}
37
38
class ConfigurationTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
39
40
val settings: TestKitSettings = TestKitExtension(system)
41
42
"TestKit settings" should {
43
"provide access to configuration values" in {
44
println(s"Test time factor: ${settings.TestTimeFactor}")
45
println(s"Default timeout: ${settings.SingleExpectDefaultTimeout}")
46
println(s"Event filter leeway: ${settings.TestEventFilterLeeway}")
47
println(s"Default Timeout: ${settings.DefaultTimeout}")
48
49
// Use settings in tests
50
within(settings.SingleExpectDefaultTimeout) {
51
testActor ! "ping"
52
expectMsg("pong")
53
}
54
}
55
}
56
}
57
```
58
59
#### Custom Configuration
60
61
```scala
62
import com.typesafe.config.ConfigFactory
63
64
// Custom test configuration
65
val testConfig = ConfigFactory.parseString("""
66
akka {
67
test {
68
# Scale factor for test timeouts
69
timefactor = 2.0
70
71
# Default timeout for single message expectations
72
single-expect-default = 5s
73
74
# Leeway for EventFilter operations
75
filter-leeway = 3s
76
77
# Default timeout for operations requiring implicit Timeout
78
default-timeout = 10s
79
}
80
}
81
""")
82
83
val system = ActorSystem("CustomTestSystem", testConfig)
84
val settings = TestKitExtension(system)
85
86
// Settings reflect custom configuration
87
assert(settings.TestTimeFactor == 2.0)
88
assert(settings.SingleExpectDefaultTimeout == 5.seconds)
89
```
90
91
#### Time Factor Usage
92
93
```scala
94
class TimeFactorTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
95
96
"Time factor" should {
97
"scale test durations automatically" in {
98
val settings = TestKitExtension(system)
99
val baseTimeout = 1.second
100
101
// Manual scaling
102
val scaledTimeout = Duration.fromNanos(
103
(baseTimeout.toNanos * settings.TestTimeFactor).toLong
104
)
105
106
// Or use TestDuration implicit class
107
import akka.testkit.TestDuration._
108
val dilatedTimeout = baseTimeout.dilated
109
110
assert(scaledTimeout == dilatedTimeout)
111
112
// Use in actual test
113
within(dilatedTimeout) {
114
someSlowActor ! "slow-operation"
115
expectMsg("completed")
116
}
117
}
118
}
119
}
120
```
121
122
## Timing Utilities
123
124
### TestDuration Implicit Class { .api }
125
126
```scala
127
// In akka.testkit package object
128
implicit class TestDuration(val duration: FiniteDuration) extends AnyVal {
129
def dilated(implicit system: ActorSystem): FiniteDuration
130
}
131
```
132
133
Provides automatic time scaling for test durations based on the configured time factor.
134
135
### Usage Examples
136
137
#### Duration Scaling
138
139
```scala
140
import akka.testkit.TestDuration._
141
import scala.concurrent.duration._
142
143
class DurationScalingTest extends TestKit(ActorSystem("TestSystem")) {
144
145
"Duration scaling" should {
146
"automatically adjust timeouts" in {
147
// Base durations
148
val shortTimeout = 100.millis
149
val mediumTimeout = 1.second
150
val longTimeout = 5.seconds
151
152
// Scaled versions (uses TestTimeFactor from config)
153
val scaledShort = shortTimeout.dilated
154
val scaledMedium = mediumTimeout.dilated
155
val scaledLong = longTimeout.dilated
156
157
println(s"Short: ${shortTimeout} -> ${scaledShort}")
158
println(s"Medium: ${mediumTimeout} -> ${scaledMedium}")
159
println(s"Long: ${longTimeout} -> ${scaledLong}")
160
161
// Use in tests
162
within(scaledMedium) {
163
slowActor ! "request"
164
expectMsg(scaledShort, "response")
165
}
166
}
167
}
168
}
169
```
170
171
#### Conditional Scaling
172
173
```scala
174
class ConditionalScalingTest extends TestKit(ActorSystem("TestSystem")) {
175
176
"Conditional scaling" should {
177
"scale only when needed" in {
178
val baseTimeout = 2.seconds
179
val settings = TestKitExtension(system)
180
181
// Only scale if time factor is not 1.0
182
val effectiveTimeout = if (settings.TestTimeFactor != 1.0) {
183
baseTimeout.dilated
184
} else {
185
baseTimeout
186
}
187
188
within(effectiveTimeout) {
189
testActor ! "message"
190
expectMsg("response")
191
}
192
}
193
}
194
}
195
```
196
197
## Network Utilities
198
199
### SocketUtil { .api }
200
201
```scala
202
object SocketUtil {
203
def temporaryLocalPort(udp: Boolean = false): Int
204
def temporaryLocalPort(protocol: Protocol): Int
205
def temporaryServerAddress(address: String = "127.0.0.1", udp: Boolean = false): InetSocketAddress
206
def temporaryServerAddresses(numberOfAddresses: Int, hostname: String = "127.0.0.1", udp: Boolean = false): immutable.IndexedSeq[InetSocketAddress]
207
208
sealed trait Protocol
209
case object Tcp extends Protocol
210
case object Udp extends Protocol
211
case object Both extends Protocol
212
}
213
```
214
215
Utilities for obtaining temporary network ports and addresses for testing network-based actors and services.
216
217
### Usage Examples
218
219
#### Basic Port Allocation
220
221
```scala
222
import akka.testkit.SocketUtil
223
import java.net.InetSocketAddress
224
225
class NetworkTest extends TestKit(ActorSystem("TestSystem")) {
226
227
"SocketUtil" should {
228
"provide temporary ports for testing" in {
229
// Get temporary TCP port
230
val tcpPort = SocketUtil.temporaryLocalPort(udp = false)
231
println(s"TCP port: $tcpPort")
232
233
// Get temporary UDP port
234
val udpPort = SocketUtil.temporaryLocalPort(udp = true)
235
println(s"UDP port: $udpPort")
236
237
// Get port by protocol
238
val tcpPort2 = SocketUtil.temporaryLocalPort(SocketUtil.Tcp)
239
val udpPort2 = SocketUtil.temporaryLocalPort(SocketUtil.Udp)
240
241
// All ports should be different
242
assert(tcpPort != udpPort)
243
assert(tcpPort != tcpPort2)
244
assert(udpPort != udpPort2)
245
}
246
247
"provide temporary addresses" in {
248
// Get temporary server address
249
val address = SocketUtil.temporaryServerAddress()
250
println(s"Address: ${address.getHostString}:${address.getPort}")
251
252
// Custom hostname
253
val customAddress = SocketUtil.temporaryServerAddress("localhost", udp = false)
254
assert(customAddress.getHostString == "localhost")
255
256
// Multiple addresses
257
val addresses = SocketUtil.temporaryServerAddresses(3, "127.0.0.1", udp = false)
258
assert(addresses.length == 3)
259
addresses.foreach(addr => println(s"Address: ${addr}"))
260
}
261
}
262
}
263
```
264
265
#### Testing Network Actors
266
267
```scala
268
import akka.actor.{Actor, Props}
269
import akka.io.{IO, Tcp}
270
import java.net.InetSocketAddress
271
272
class NetworkActor extends Actor {
273
import Tcp._
274
import context.system
275
276
def receive = {
277
case "bind" =>
278
val port = SocketUtil.temporaryLocalPort()
279
val address = new InetSocketAddress("localhost", port)
280
IO(Tcp) ! Bind(self, address)
281
282
case b @ Bound(localAddress) =>
283
sender() ! s"bound:${localAddress.getPort}"
284
285
case CommandFailed(cmd) =>
286
sender() ! s"failed:${cmd}"
287
}
288
}
289
290
class NetworkActorTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
291
292
"NetworkActor" should {
293
"bind to temporary port" in {
294
val actor = system.actorOf(Props[NetworkActor])
295
296
actor ! "bind"
297
298
// Expect successful binding with port number
299
val response = expectMsgPF(5.seconds) {
300
case msg: String if msg.startsWith("bound:") => msg
301
}
302
303
val port = response.split(":")(1).toInt
304
assert(port > 0)
305
println(s"Successfully bound to port: $port")
306
}
307
}
308
}
309
```
310
311
#### Multi-Node Testing Setup
312
313
```scala
314
class MultiNodeTest extends TestKit(ActorSystem("TestSystem")) {
315
316
"Multi-node setup" should {
317
"allocate unique ports for each node" in {
318
val nodeCount = 3
319
val ports = (1 to nodeCount).map(_ => SocketUtil.temporaryLocalPort())
320
321
// All ports should be unique
322
assert(ports.distinct.length == nodeCount)
323
324
// Create addresses for each node
325
val addresses = ports.map(port => new InetSocketAddress("127.0.0.1", port))
326
327
addresses.zipWithIndex.foreach { case (addr, index) =>
328
println(s"Node $index: ${addr}")
329
}
330
331
// Use addresses to configure cluster nodes or test network communication
332
// ...
333
}
334
}
335
}
336
```
337
338
## Serialization Utilities
339
340
### TestMessageSerializer { .api }
341
342
```scala
343
class TestMessageSerializer(system: ExtendedActorSystem) extends Serializer {
344
def identifier: Int
345
def includeManifest: Boolean
346
def toBinary(obj: AnyRef): Array[Byte]
347
def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]]): AnyRef
348
}
349
```
350
351
A serializer designed for test messages that uses Java serialization for simplicity.
352
353
### JavaSerializable { .api }
354
355
```scala
356
trait JavaSerializable extends Serializable
357
```
358
359
Marker trait indicating that a message should use Java serialization in test environments.
360
361
### Usage Examples
362
363
#### Testing Message Serialization
364
365
```scala
366
import akka.serialization.SerializationExtension
367
368
// Test message with JavaSerializable marker
369
case class TestMessage(data: String, count: Int) extends JavaSerializable
370
371
class SerializationTest extends TestKit(ActorSystem("TestSystem")) {
372
373
"Message serialization" should {
374
"serialize and deserialize test messages" in {
375
val serialization = SerializationExtension(system)
376
val originalMessage = TestMessage("test-data", 42)
377
378
// Serialize
379
val serializer = serialization.findSerializerFor(originalMessage)
380
val bytes = serializer.toBinary(originalMessage)
381
println(s"Serialized to ${bytes.length} bytes")
382
383
// Deserialize
384
val deserializedMessage = serializer.fromBinary(bytes, Some(originalMessage.getClass))
385
386
// Verify
387
assert(deserializedMessage == originalMessage)
388
println(s"Successfully round-tripped: $deserializedMessage")
389
}
390
}
391
}
392
```
393
394
#### Custom Test Serializer Configuration
395
396
```scala
397
// Configure custom serializer in test configuration
398
val testConfig = ConfigFactory.parseString("""
399
akka {
400
actor {
401
serializers {
402
test = "akka.testkit.TestMessageSerializer"
403
}
404
serialization-bindings {
405
"akka.testkit.JavaSerializable" = test
406
}
407
}
408
}
409
""")
410
411
class CustomSerializationTest extends TestKit(ActorSystem("TestSystem", testConfig)) {
412
413
"Custom serialization" should {
414
"use TestMessageSerializer for JavaSerializable messages" in {
415
val serialization = SerializationExtension(system)
416
val message = TestMessage("serialized", 123)
417
418
val serializer = serialization.findSerializerFor(message)
419
assert(serializer.isInstanceOf[TestMessageSerializer])
420
421
// Test serialization roundtrip
422
val bytes = serializer.toBinary(message)
423
val restored = serializer.fromBinary(bytes, Some(message.getClass))
424
assert(restored == message)
425
}
426
}
427
}
428
```
429
430
## Configuration Management
431
432
### Test Configuration Patterns
433
434
#### Environment-Specific Configuration
435
436
```scala
437
import com.typesafe.config.{Config, ConfigFactory}
438
439
object TestConfiguration {
440
441
def forEnvironment(env: String): Config = {
442
val baseConfig = ConfigFactory.load("application-test.conf")
443
val envConfig = ConfigFactory.load(s"application-test-$env.conf")
444
envConfig.withFallback(baseConfig)
445
}
446
447
def withCustomDispatcher(): Config = {
448
ConfigFactory.parseString("""
449
akka {
450
actor {
451
default-dispatcher {
452
type = akka.testkit.CallingThreadDispatcherConfigurator
453
}
454
}
455
}
456
""")
457
}
458
459
def withTestScheduler(): Config = {
460
ConfigFactory.parseString("""
461
akka {
462
scheduler {
463
implementation = akka.testkit.ExplicitlyTriggeredScheduler
464
}
465
}
466
""")
467
}
468
469
def deterministicTestConfig(): Config = {
470
withCustomDispatcher()
471
.withFallback(withTestScheduler())
472
.withFallback(ConfigFactory.load("application-test.conf"))
473
}
474
}
475
476
// Usage in tests
477
class ConfigurableTest extends TestKit(ActorSystem("TestSystem", TestConfiguration.deterministicTestConfig())) {
478
479
"Configurable test" should {
480
"use deterministic execution" in {
481
// Test runs with CallingThreadDispatcher and ExplicitlyTriggeredScheduler
482
val actor = system.actorOf(Props[MyActor])
483
actor ! "test"
484
expectMsg("response") // Deterministic execution
485
}
486
}
487
}
488
```
489
490
#### Dynamic Configuration
491
492
```scala
493
import scala.util.Random
494
495
class DynamicConfigurationTest {
496
497
def createTestSystem(customSettings: Map[String, Any] = Map.empty): ActorSystem = {
498
val baseConfig = Map(
499
"akka.test.timefactor" -> 1.0,
500
"akka.test.single-expect-default" -> "3s",
501
"akka.test.filter-leeway" -> "3s"
502
)
503
504
val finalSettings = baseConfig ++ customSettings
505
val configString = finalSettings.map {
506
case (key, value) => s"$key = $value"
507
}.mkString("\n")
508
509
val config = ConfigFactory.parseString(configString)
510
.withFallback(ConfigFactory.load("application-test.conf"))
511
512
ActorSystem(s"TestSystem-${Random.nextInt(1000)}", config)
513
}
514
515
@Test
516
def testWithCustomTimeout(): Unit = {
517
val system = createTestSystem(Map(
518
"akka.test.single-expect-default" -> "10s",
519
"akka.test.timefactor" -> 2.0
520
))
521
522
new TestKit(system) {
523
val settings = TestKitExtension(system)
524
assert(settings.SingleExpectDefaultTimeout == 10.seconds)
525
assert(settings.TestTimeFactor == 2.0)
526
527
TestKit.shutdownActorSystem(system)
528
}
529
}
530
}
531
```
532
533
## Best Practices
534
535
### Configuration Best Practices
536
537
1. **Separate test configuration**: Use dedicated test configuration files
538
2. **Environment-specific settings**: Create configurations for different test environments
539
3. **Use time factors**: Scale timeouts appropriately for different environments
540
4. **Document settings**: Comment configuration values and their purposes
541
542
```scala
543
// Good: Well-documented test configuration
544
akka {
545
test {
546
# Scale timeouts by 2x for slower CI environments
547
timefactor = 2.0
548
549
# Longer default timeout for integration tests
550
single-expect-default = 5s
551
552
# Allow more time for event filter operations
553
filter-leeway = 3s
554
}
555
556
# Use deterministic dispatcher for unit tests
557
actor.default-dispatcher.type = akka.testkit.CallingThreadDispatcherConfigurator
558
559
# Suppress expected log messages
560
loggers = ["akka.testkit.TestEventListener"]
561
loglevel = "WARNING"
562
log-dead-letters = off
563
}
564
```
565
566
### Network Testing Best Practices
567
568
1. **Use temporary ports**: Always use SocketUtil for port allocation
569
2. **Clean up resources**: Ensure network resources are released after tests
570
3. **Test port conflicts**: Verify that multiple test runs don't conflict
571
4. **Handle binding failures**: Account for port binding failures in tests
572
573
```scala
574
// Good: Proper network resource management
575
class NetworkResourceTest extends TestKit(ActorSystem("TestSystem")) with BeforeAndAfterEach {
576
577
var allocatedPorts: List[Int] = List.empty
578
579
override def beforeEach(): Unit = {
580
allocatedPorts = List.empty
581
}
582
583
override def afterEach(): Unit = {
584
// Log allocated ports for debugging
585
if (allocatedPorts.nonEmpty) {
586
println(s"Test used ports: ${allocatedPorts.mkString(", ")}")
587
}
588
}
589
590
def getTestPort(): Int = {
591
val port = SocketUtil.temporaryLocalPort()
592
allocatedPorts = port :: allocatedPorts
593
port
594
}
595
}
596
```
597
598
### Serialization Testing Best Practices
599
600
1. **Test serialization boundaries**: Verify messages crossing serialization boundaries
601
2. **Use JavaSerializable for tests**: Simplify test message serialization
602
3. **Test large messages**: Verify behavior with large serialized messages
603
4. **Measure serialization performance**: Monitor serialization overhead in tests
604
605
```scala
606
// Good: Comprehensive serialization testing
607
case class LargeTestMessage(data: Array[Byte]) extends JavaSerializable
608
609
class SerializationPerformanceTest extends TestKit(ActorSystem("TestSystem")) {
610
611
"Serialization performance" should {
612
"handle large messages efficiently" in {
613
val serialization = SerializationExtension(system)
614
val largeData = Array.fill(1024 * 1024)(0.toByte) // 1MB
615
val message = LargeTestMessage(largeData)
616
617
val startTime = System.nanoTime()
618
val serializer = serialization.findSerializerFor(message)
619
val bytes = serializer.toBinary(message)
620
val deserializedMessage = serializer.fromBinary(bytes, Some(message.getClass))
621
val endTime = System.nanoTime()
622
623
val durationMs = (endTime - startTime) / 1000000
624
println(s"Serialization roundtrip took ${durationMs}ms for ${bytes.length} bytes")
625
626
assert(deserializedMessage.asInstanceOf[LargeTestMessage].data.sameElements(largeData))
627
assert(durationMs < 1000) // Should complete within 1 second
628
}
629
}
630
}
631
```