0
# Test Utilities and Helpers
1
2
Akka TestKit provides various utility classes, actors, and functions for common testing patterns including pre-built test actors, network utilities, serialization support, and configuration helpers.
3
4
## Capabilities
5
6
### Test Actors
7
8
Collection of pre-built actor implementations for common testing patterns and scenarios.
9
10
```scala { .api }
11
object TestActors {
12
/**
13
* Props for EchoActor - sends back received messages unmodified
14
* Useful for testing message routing and actor communication
15
*/
16
val echoActorProps: Props
17
18
/**
19
* Props for BlackholeActor - ignores all incoming messages
20
* Useful for testing scenarios where messages should be discarded
21
*/
22
val blackholeProps: Props
23
24
/**
25
* Props for ForwardActor - forwards all messages to specified ActorRef
26
* @param ref Target ActorRef to forward messages to
27
* @return Props for ForwardActor configured with target
28
*/
29
def forwardActorProps(ref: ActorRef): Props
30
}
31
32
/**
33
* Actor that echoes back all received messages
34
* Maintains original sender for proper request-response patterns
35
*/
36
class EchoActor extends Actor {
37
def receive: Receive = {
38
case message => sender() ! message
39
}
40
}
41
42
/**
43
* Actor that ignores all incoming messages
44
* Useful for dead-end routing and message sink scenarios
45
*/
46
class BlackholeActor extends Actor {
47
def receive: Receive = {
48
case _ => // Ignore all messages
49
}
50
}
51
52
/**
53
* Actor that forwards all messages to a specified target
54
* Maintains original sender for proper message forwarding
55
* @param ref Target ActorRef to forward messages to
56
*/
57
class ForwardActor(ref: ActorRef) extends Actor {
58
def receive: Receive = {
59
case message => ref.forward(message)
60
}
61
}
62
```
63
64
### Network Utilities
65
66
Utilities for network testing including port allocation and address management.
67
68
```scala { .api }
69
object SocketUtil {
70
/**
71
* Protocol types for network testing
72
*/
73
sealed trait Protocol
74
case object Tcp extends Protocol
75
case object Udp extends Protocol
76
case object Both extends Protocol
77
78
/**
79
* Constant for random loopback address allocation
80
*/
81
val RANDOM_LOOPBACK_ADDRESS = "RANDOM_LOOPBACK_ADDRESS"
82
83
/**
84
* Get a temporary free local port
85
* @param udp Whether to get UDP port (default: TCP)
86
* @return Available port number
87
*/
88
def temporaryLocalPort(udp: Boolean = false): Int
89
90
/**
91
* Get a temporary free local port for specific protocol
92
* @param protocol Protocol type (Tcp, Udp, or Both)
93
* @return Available port number that's free for the specified protocol
94
*/
95
def temporaryLocalPort(protocol: Protocol): Int
96
97
/**
98
* Get a temporary server address with free port
99
* @param address Hostname or IP address (use RANDOM_LOOPBACK_ADDRESS for random)
100
* @param udp Whether to get UDP address (default: TCP)
101
* @return InetSocketAddress with free port
102
*/
103
def temporaryServerAddress(address: String = "localhost", udp: Boolean = false): InetSocketAddress
104
105
/**
106
* Get hostname and port for temporary server
107
* @param interface Network interface name
108
* @return Tuple of (hostname, port)
109
*/
110
def temporaryServerHostnameAndPort(interface: String = "localhost"): (String, Int)
111
112
/**
113
* Get multiple temporary server addresses
114
* @param numberOfAddresses Number of addresses to generate
115
* @param hostname Hostname for all addresses
116
* @param udp Whether to get UDP addresses (default: TCP)
117
* @return IndexedSeq of InetSocketAddress instances with free ports
118
*/
119
def temporaryServerAddresses(numberOfAddresses: Int, hostname: String = "localhost",
120
udp: Boolean = false): immutable.IndexedSeq[InetSocketAddress]
121
122
/**
123
* Get server address that is not bound to any service
124
* @param address Hostname or IP address
125
* @return InetSocketAddress with available port
126
*/
127
def notBoundServerAddress(address: String = "localhost"): InetSocketAddress
128
129
/**
130
* Get server address that is not bound to any service (parameterless version)
131
* @return InetSocketAddress with localhost and available port
132
*/
133
def notBoundServerAddress(): InetSocketAddress
134
135
/**
136
* Get temporary UDP IPv6 port for specific network interface
137
* @param iface Network interface to bind to
138
* @return Available IPv6 UDP port number
139
*/
140
def temporaryUdpIpv6Port(iface: NetworkInterface): Int
141
}
142
```
143
144
### Test Exception
145
146
Predefined exception class for testing without stack trace overhead.
147
148
```scala { .api }
149
/**
150
* Predefined exception for testing scenarios
151
* Extends NoStackTrace to avoid stack trace generation overhead in tests
152
* @param message Exception message
153
*/
154
case class TestException(message: String) extends RuntimeException(message) with NoStackTrace
155
156
/**
157
* Companion object for TestException
158
*/
159
object TestException {
160
/**
161
* Create TestException with default message
162
* @return TestException with "test" message
163
*/
164
def apply(): TestException = TestException("test")
165
}
166
```
167
168
### Serialization Support
169
170
Utilities for testing serialization scenarios with Java serialization.
171
172
```scala { .api }
173
/**
174
* Java serialization support for ad-hoc test messages
175
* Useful when testing serialization without defining custom serializers
176
* @param system Extended actor system for serializer integration
177
*/
178
class TestJavaSerializer(val system: ExtendedActorSystem) extends BaseSerializer {
179
/**
180
* Serializer identifier for Akka serialization system
181
*/
182
def identifier: Int = 42
183
184
/**
185
* Serialize object to byte array using Java serialization
186
* @param o Object to serialize
187
* @return Serialized byte array
188
*/
189
def toBinary(o: AnyRef): Array[Byte]
190
191
/**
192
* Deserialize byte array to object using Java serialization
193
* @param bytes Serialized byte array
194
* @param clazz Optional class hint for deserialization
195
* @return Deserialized object
196
*/
197
def fromBinary(bytes: Array[Byte], clazz: Option[Class[_]]): AnyRef
198
199
/**
200
* Include manifest information for deserialization
201
*/
202
def includeManifest: Boolean = false
203
}
204
205
/**
206
* Marker trait for test messages that should use Java serialization
207
* Mix into test message classes to enable automatic Java serialization
208
*/
209
trait JavaSerializable extends Serializable {
210
// Marker trait - no additional methods
211
}
212
```
213
214
### Configuration and Extensions
215
216
TestKit configuration settings and extension utilities.
217
218
```scala { .api }
219
/**
220
* TestKit configuration settings
221
* @param config Configuration object with test settings
222
*/
223
class TestKitSettings(config: Config) {
224
/**
225
* Time scaling factor for tests - multiplies all timeouts
226
* Useful for running tests on slower systems
227
*/
228
val TestTimeFactor: Double
229
230
/**
231
* Default timeout for single message expectations
232
*/
233
val SingleExpectDefaultTimeout: FiniteDuration
234
235
/**
236
* Default timeout for expectNoMessage operations
237
*/
238
val ExpectNoMessageDefaultTimeout: FiniteDuration
239
240
/**
241
* Grace period for event filters to complete
242
*/
243
val TestEventFilterLeeway: FiniteDuration
244
245
/**
246
* Default Akka timeout for various operations
247
*/
248
val DefaultTimeout: Timeout
249
}
250
251
/**
252
* Extension for accessing TestKit settings
253
*/
254
object TestKitExtension extends ExtensionId[TestKitSettings] with ExtensionIdProvider {
255
/**
256
* Get TestKit settings for actor system
257
* @param system ActorSystem to get settings for
258
* @return TestKitSettings instance
259
*/
260
def get(system: ActorSystem): TestKitSettings
261
262
def lookup: TestKitExtension.type = TestKitExtension
263
264
def createExtension(system: ExtendedActorSystem): TestKitSettings =
265
new TestKitSettings(system.settings.config)
266
}
267
```
268
269
### Utility Functions
270
271
Miscellaneous utility functions for TestKit operations.
272
273
```scala { .api }
274
/**
275
* Utility functions for TestKit operations
276
*/
277
object TestKitUtils {
278
// Various internal utility functions for TestKit implementation
279
// Includes timing calculations, message queue management, etc.
280
}
281
```
282
283
### Implicit Traits
284
285
Commonly used traits that provide implicit values for testing convenience.
286
287
```scala { .api }
288
/**
289
* Automatically provides testActor as implicit sender
290
* Mix into test classes to avoid explicitly specifying sender
291
*/
292
trait ImplicitSender extends TestKitBase {
293
/**
294
* Implicit sender reference - uses testActor
295
*/
296
implicit def self: ActorRef = testActor
297
}
298
299
/**
300
* Provides default timeout from TestKit settings
301
* Mix into test classes for consistent timeout behavior
302
*/
303
trait DefaultTimeout extends TestKitBase {
304
/**
305
* Implicit timeout from TestKit configuration
306
*/
307
implicit val timeout: Timeout = testKitSettings.DefaultTimeout
308
}
309
```
310
311
### Time Dilation Support
312
313
Support for scaling time in test scenarios.
314
315
```scala { .api }
316
/**
317
* Implicit class for time dilation in tests
318
* Allows scaling of durations based on test time factor
319
* @param duration Original duration to scale
320
*/
321
implicit class TestDuration(val duration: FiniteDuration) extends AnyVal {
322
/**
323
* Scale duration by test time factor
324
* @param system Implicit ActorSystem for accessing time factor
325
* @return Scaled duration based on TestTimeFactor configuration
326
*/
327
def dilated(implicit system: ActorSystem): FiniteDuration =
328
Duration.fromNanos((duration.toNanos * TestKitExtension(system).TestTimeFactor + 0.5).toLong)
329
}
330
```
331
332
**Usage Examples:**
333
334
```scala
335
import akka.actor.{Actor, ActorSystem, Props}
336
import akka.testkit.{TestKit, TestActors, SocketUtil, TestException}
337
import java.net.InetSocketAddress
338
339
class TestUtilitiesExample extends TestKit(ActorSystem("test")) {
340
341
"TestActors" should {
342
"provide EchoActor for message echoing" in {
343
val echo = system.actorOf(TestActors.echoActorProps)
344
345
echo ! "hello"
346
expectMsg("hello")
347
348
echo ! 42
349
expectMsg(42)
350
351
echo ! List(1, 2, 3)
352
expectMsg(List(1, 2, 3))
353
}
354
355
"provide BlackholeActor for message ignoring" in {
356
val blackhole = system.actorOf(TestActors.blackholeProps)
357
358
// Messages are ignored - no response expected
359
blackhole ! "message1"
360
blackhole ! "message2"
361
blackhole ! "message3"
362
363
expectNoMessage(100.millis)
364
}
365
366
"provide ForwardActor for message forwarding" in {
367
val probe = TestProbe()
368
val forwarder = system.actorOf(TestActors.forwardActorProps(probe.ref))
369
370
forwarder ! "forwarded-message"
371
probe.expectMsg("forwarded-message")
372
373
// Original sender is maintained
374
probe.reply("response")
375
expectMsg("response")
376
}
377
}
378
379
"SocketUtil" should {
380
"provide free ports for network testing" in {
381
// Get free TCP port
382
val tcpPort = SocketUtil.temporaryLocalPort()
383
assert(tcpPort > 0 && tcpPort < 65536)
384
385
// Get free UDP port
386
val udpPort = SocketUtil.temporaryLocalPort(udp = true)
387
assert(udpPort > 0 && udpPort < 65536)
388
389
// Get port free for both protocols
390
val bothPort = SocketUtil.temporaryLocalPort(SocketUtil.Both)
391
assert(bothPort > 0 && bothPort < 65536)
392
}
393
394
"provide server addresses with free ports" in {
395
val address = SocketUtil.temporaryServerAddress("localhost", udp = false)
396
assert(address.getHostName == "localhost")
397
assert(address.getPort > 0)
398
399
// Multiple addresses
400
val addresses = SocketUtil.temporaryServerAddresses(3, "127.0.0.1", udp = false)
401
assert(addresses.length == 3)
402
addresses.foreach { addr =>
403
assert(addr.getHostName == "127.0.0.1")
404
assert(addr.getPort > 0)
405
}
406
407
// All ports should be different
408
val ports = addresses.map(_.getPort).toSet
409
assert(ports.size == 3)
410
}
411
}
412
413
"TestException" should {
414
"provide exception without stack trace overhead" in {
415
val exception = TestException("Test error message")
416
417
assert(exception.getMessage == "Test error message")
418
assert(exception.isInstanceOf[NoStackTrace])
419
420
// Use in test scenarios
421
intercept[TestException] {
422
throw TestException("Expected test failure")
423
}
424
}
425
}
426
427
"ImplicitSender trait" should {
428
"provide automatic sender" in {
429
// When mixing in ImplicitSender, testActor is used as sender automatically
430
val echo = system.actorOf(TestActors.echoActorProps)
431
432
echo ! "test-message"
433
expectMsg("test-message")
434
435
// No need to specify sender explicitly
436
}
437
}
438
439
"Time dilation" should {
440
"scale timeouts based on configuration" in {
441
import scala.concurrent.duration._
442
443
// Original duration
444
val original = 1.second
445
446
// Dilated duration (scaled by TestTimeFactor)
447
val dilated = original.dilated
448
449
// Use dilated timeouts in tests
450
within(dilated) {
451
// Test operations that should complete within scaled time
452
Thread.sleep(500) // This would work even if TestTimeFactor > 1
453
}
454
}
455
}
456
}
457
```
458
459
### Advanced Testing Patterns
460
461
Complex testing scenarios using multiple utilities together.
462
463
```scala
464
class AdvancedUtilitiesExample extends TestKit(ActorSystem("test")) {
465
466
class NetworkActor(host: String, port: Int) extends Actor {
467
def receive = {
468
case "connect" =>
469
// Simulate network connection
470
sender() ! s"connected-to-$host:$port"
471
case "disconnect" =>
472
sender() ! "disconnected"
473
case msg =>
474
sender() ! s"sent-$msg-to-$host:$port"
475
}
476
}
477
478
"Combined utilities" should {
479
"support complex network testing scenarios" in {
480
// Get free port for test server
481
val serverPort = SocketUtil.temporaryLocalPort()
482
val serverAddress = SocketUtil.temporaryServerAddress("localhost", udp = false)
483
484
// Create network actor with test port
485
val networkActor = system.actorOf(Props(new NetworkActor("localhost", serverPort)))
486
487
// Create echo actor for response testing
488
val echo = system.actorOf(TestActors.echoActorProps)
489
490
// Test network connection
491
networkActor ! "connect"
492
expectMsg(s"connected-to-localhost:$serverPort")
493
494
// Test message forwarding through network
495
val forwarder = system.actorOf(TestActors.forwardActorProps(echo))
496
forwarder ! "network-message"
497
expectMsg("network-message")
498
}
499
500
"support error testing with custom exceptions" in {
501
val actor = system.actorOf(Props(new Actor {
502
def receive = {
503
case "fail" => throw TestException("Simulated failure")
504
case msg => sender() ! s"processed-$msg"
505
}
506
}))
507
508
// Normal operation
509
actor ! "normal"
510
expectMsg("processed-normal")
511
512
// Error scenario with TestException
513
EventFilter[TestException](message = "Simulated failure").intercept {
514
actor ! "fail"
515
}
516
}
517
}
518
}
519
```
520
521
### Configuration Usage
522
523
Examples of using TestKit configuration and extensions.
524
525
```scala
526
class ConfigurationUsageExample extends TestKit(ActorSystem("test")) {
527
528
"TestKit configuration" should {
529
"provide access to test settings" in {
530
val settings = TestKitExtension(system)
531
532
println(s"Test time factor: ${settings.TestTimeFactor}")
533
println(s"Default timeout: ${settings.DefaultTimeout}")
534
println(s"Single expect timeout: ${settings.SingleExpectDefaultTimeout}")
535
536
// Use settings in test logic
537
val scaledTimeout = (1.second.toNanos * settings.TestTimeFactor).nanos
538
within(scaledTimeout) {
539
// Perform time-sensitive test operations
540
}
541
}
542
543
"support custom configuration" in {
544
import com.typesafe.config.ConfigFactory
545
546
val customConfig = ConfigFactory.parseString("""
547
akka.test {
548
timefactor = 2.0
549
single-expect-default = 10s
550
default-timeout = 15s
551
}
552
""")
553
554
val customSystem = ActorSystem("custom-test", customConfig)
555
val customSettings = TestKitExtension(customSystem)
556
557
assert(customSettings.TestTimeFactor == 2.0)
558
assert(customSettings.SingleExpectDefaultTimeout == 10.seconds)
559
560
TestKit.shutdownActorSystem(customSystem)
561
}
562
}
563
}
564
```
565
566
### Best Practices
567
568
Guidelines for effective use of TestKit utilities.
569
570
```scala
571
// GOOD: Use appropriate test actors for different scenarios
572
val echo = system.actorOf(TestActors.echoActorProps) // For response testing
573
val blackhole = system.actorOf(TestActors.blackholeProps) // For fire-and-forget testing
574
val forwarder = system.actorOf(TestActors.forwardActorProps(probe.ref)) // For routing testing
575
576
// GOOD: Get free ports for network tests to avoid conflicts
577
val port = SocketUtil.temporaryLocalPort()
578
val server = startTestServer(port)
579
580
// GOOD: Use TestException for expected test failures
581
def simulateError(): Unit = throw TestException("Expected test error")
582
583
// GOOD: Scale timeouts appropriately
584
val timeout = 5.seconds.dilated // Scales with TestTimeFactor
585
586
// GOOD: Mix in helpful traits
587
class MyTest extends TestKit(ActorSystem("test")) with ImplicitSender with DefaultTimeout
588
589
// AVOID: Hardcoded ports that might conflict
590
// val server = startTestServer(8080) // BAD - might be in use
591
592
// AVOID: Using real exceptions for expected test failures
593
// throw new RuntimeException("test") // BAD - generates unnecessary stack traces
594
```