Akka TestKit - toolkit for testing Actor systems
Additional utility functions and classes for common testing scenarios, including socket utilities, test exceptions, and serialization helpers.
Utilities for getting free socket addresses in tests.
object SocketUtil {
// Port utilities
def temporaryLocalPort(udp: Boolean = false): Int
def temporaryLocalAddress(udp: Boolean = false): InetSocketAddress
// Server address utilities
def temporaryServerAddress(address: String = "127.0.0.1", udp: Boolean = false): InetSocketAddress
def temporaryServerAddresses(
numberOfAddresses: Int,
hostname: String = "127.0.0.1",
udp: Boolean = false
): Seq[InetSocketAddress]
// Unbound address utility
def notBoundServerAddress(address: String = "127.0.0.1", udp: Boolean = false): InetSocketAddress
}Usage Examples:
import akka.testkit.SocketUtil
class NetworkingTest extends TestKit(ActorSystem("TestSystem")) {
"SocketUtil" should {
"provide free TCP ports" in {
val port1 = SocketUtil.temporaryLocalPort()
val port2 = SocketUtil.temporaryLocalPort()
port1 should not equal port2
port1 should be > 1024
port2 should be > 1024
// Use ports for testing
val server = startTestServer(port1)
val client = connectToServer("localhost", port1)
// Test networking code
client.send("hello")
server.expectReceive("hello")
}
"provide free UDP ports" in {
val udpPort = SocketUtil.temporaryLocalPort(udp = true)
val udpAddress = SocketUtil.temporaryLocalAddress(udp = true)
udpAddress.getPort should equal(udpPort)
// Use for UDP testing
val udpSocket = new DatagramSocket(udpPort)
// Test UDP functionality
}
"provide server addresses" in {
val address = SocketUtil.temporaryServerAddress("0.0.0.0")
address.getHostString should be("0.0.0.0")
address.getPort should be > 1024
// Multiple addresses for cluster testing
val addresses = SocketUtil.temporaryServerAddresses(3, "127.0.0.1")
addresses should have size 3
addresses.map(_.getPort).distinct should have size 3
}
}
}Predefined exception for tests without stack trace.
final case class TestException(message: String) extends RuntimeException(message) with NoStackTraceUsage Examples:
import akka.testkit.TestException
class ExceptionTest extends TestKit(ActorSystem("TestSystem")) {
"TestException" should {
"be lightweight for testing" in {
val exception = TestException("Test error message")
exception.getMessage should be("Test error message")
exception.getStackTrace should be(empty) // No stack trace for performance
// Use in actor testing
EventFilter[TestException](message = "Test error message").intercept {
val actor = system.actorOf(Props(new Actor {
def receive = {
case "throw" => throw TestException("Test error message")
}
}))
actor ! "throw"
}
}
"be useful for expected failures" in {
class FailingActor extends Actor {
def receive = {
case "fail" => throw TestException("Expected failure")
case msg => sender() ! msg
}
}
val actor = system.actorOf(Props[FailingActor]())
// Test normal operation
actor ! "hello"
expectMsg("hello")
// Test expected failure
EventFilter[TestException](message = "Expected failure").intercept {
actor ! "fail"
}
}
}
}Utilities for Java serialization in tests.
// Marker trait for test messages using Java serialization
trait JavaSerializable
// Java serializer for ad-hoc test messages
class TestJavaSerializer extends Serializer {
def identifier: Int
def includeManifest: Boolean
def toBinary(o: AnyRef): Array[Byte]
def fromBinary(bytes: Array[Byte], manifest: Option[Class[_]]): AnyRef
}Usage Examples:
import akka.testkit.JavaSerializable
// Test message with Java serialization
case class TestMessage(data: String, id: Int) extends JavaSerializable
class SerializationTest extends TestKit(ActorSystem("TestSystem")) {
"Java serialization" should {
"work with test messages" in {
val message = TestMessage("test data", 123)
val actor = system.actorOf(Props(new Actor {
def receive = {
case msg: TestMessage => sender() ! msg.copy(data = msg.data.toUpperCase)
}
}))
actor ! message
expectMsg(TestMessage("TEST DATA", 123))
}
}
}Utility methods for TestKit operations.
object TestKitUtils {
// Extract test name from call stack
def testNameFromCallStack(classToStartFrom: Class[_], fallback: String): String
}Usage Example:
import akka.testkit.TestKitUtils
class UtilsTest extends TestKit(ActorSystem("TestSystem")) {
"TestKitUtils" should {
"extract test names" in {
val testName = TestKitUtils.testNameFromCallStack(classOf[UtilsTest], "default-test")
testName should include("extract test names")
// Useful for dynamic actor naming
val actor = system.actorOf(Props[EchoActor](), s"echo-$testName")
actor.path.name should startWith("echo-")
}
}
}Helper functions for test configuration management.
// Access test configuration
def testKitSettings(implicit system: ActorSystem): TestKitSettings = TestKitExtension(system)
// Time dilation helpers (from package object)
implicit class TestDuration(duration: FiniteDuration) {
def dilated(implicit system: ActorSystem): FiniteDuration
}Usage Examples:
import akka.testkit._
import scala.concurrent.duration._
class ConfigurationTest extends TestKit(ActorSystem("TestSystem")) {
"Configuration utilities" should {
"provide test settings" in {
val settings = testKitSettings
settings.TestTimeFactor should be >= 1.0
settings.SingleExpectDefaultTimeout should be > Duration.Zero
settings.DefaultTimeout.duration should be > Duration.Zero
}
"dilate time for tests" in {
val originalDuration = 1.second
val dilated = originalDuration.dilated
// Dilated duration accounts for slow test environments
dilated should be >= originalDuration
// Use dilated timeouts in tests
within(5.seconds.dilated) {
// Test code that might run slowly in CI environments
Thread.sleep(100)
expectNoMessage(100.millis.dilated)
}
}
}
}You can create custom utilities for your specific testing needs:
// Custom test utilities object
object MyTestUtils {
// Helper for creating actors with test-specific configuration
def testActorOf[T <: Actor](props: Props, name: String)(implicit system: ActorSystem): ActorRef = {
system.actorOf(props.withDispatcher("akka.test.calling-thread-dispatcher"), name)
}
// Helper for temporary files in tests
def withTempFile[T](content: String)(test: File => T): T = {
val file = File.createTempFile("test", ".tmp")
try {
Files.write(file.toPath, content.getBytes)
test(file)
} finally {
file.delete()
}
}
// Helper for timing operations
def timed[T](operation: => T): (T, FiniteDuration) = {
val start = System.nanoTime()
val result = operation
val duration = (System.nanoTime() - start).nanos
(result, duration)
}
}
// Usage in tests
class CustomUtilsTest extends TestKit(ActorSystem("TestSystem")) {
import MyTestUtils._
"Custom utilities" should {
"help with actor creation" in {
val actor = testActorOf(Props[EchoActor](), "test-echo")
actor ! "hello"
expectMsg("hello")
}
"provide temp file support" in {
withTempFile("test content") { file =>
file.exists() should be(true)
Source.fromFile(file).mkString should be("test content")
}
}
"measure timing" in {
val (result, duration) = timed {
Thread.sleep(100)
"completed"
}
result should be("completed")
duration should be >= 100.millis
}
}
}// Good: Use utilities for reliable testing
val port = SocketUtil.temporaryLocalPort()
val server = startServer(port)
try {
// Test code
} finally {
server.stop()
}
// Good: Use time dilation for robustness
within(5.seconds.dilated) {
heavyOperation()
expectMsg("completed")
}
// Good: Use TestException for expected failures
EventFilter[TestException](message = "Expected test failure").intercept {
actor ! TriggerFailure()
}Install with Tessl CLI
npx tessl i tessl/maven-com-typesafe-akka--akka-testkit-2-13