TestKit configuration system and Akka extensions for customizing test behavior, timeouts, and environment settings.
Akka extension for accessing TestKit settings and configuration.
object TestKitExtension extends ExtensionId[TestKitSettings] with ExtensionIdProvider {
def apply(system: ActorSystem): TestKitSettings
def apply(system: ExtendedActorSystem): TestKitSettings
def createExtension(system: ExtendedActorSystem): TestKitSettings
def lookup(): ExtensionId[_ <: Extension] = TestKitExtension
}Configuration settings class for TestKit behavior and timeouts.
class TestKitSettings(val config: Config) extends Extension {
import TestKitSettings._
// Core timing settings
val TestTimeFactor: Double
val SingleExpectDefaultTimeout: FiniteDuration
val MultiExpectDefaultTimeout: FiniteDuration
val ExpectNoMessageDefaultTimeout: FiniteDuration
val TestEventFilterLeeway: FiniteDuration
val DefaultTimeout: Timeout
// Utility methods
def dilated(duration: FiniteDuration): FiniteDuration
}
object TestKitSettings {
def apply(system: ActorSystem): TestKitSettings = TestKitExtension(system)
}akka.test {
# Time factor for dilating timeouts in tests
# Set higher for slow test environments (CI, etc.)
timefactor = 1.0
# Default timeout for single message expectations
single-expect-default = 3s
# Default timeout for multi-message expectations
multi-expect-default = 3s
# Default timeout for expectNoMessage calls
expect-no-message-default = 100ms
# Extra time to wait for expected log events in EventFilter
filter-leeway = 3s
# Default dispatcher for test actors
default-timeout = 5s
}Accessing Configuration:
import akka.testkit.{TestKit, TestKitExtension}
class ConfigurationTest extends TestKit(ActorSystem("TestSystem")) {
val settings = TestKitExtension(system)
"TestKit configuration" should {
"provide access to timing settings" in {
settings.TestTimeFactor should be >= 1.0
settings.SingleExpectDefaultTimeout should be > Duration.Zero
settings.MultiExpectDefaultTimeout should be > Duration.Zero
settings.ExpectNoMessageDefaultTimeout should be > Duration.Zero
settings.TestEventFilterLeeway should be > Duration.Zero
settings.DefaultTimeout.duration should be > Duration.Zero
}
"dilate durations correctly" in {
val original = 1.second
val dilated = settings.dilated(original)
dilated should be >= original
dilated should equal(Duration.fromNanos((original.toNanos * settings.TestTimeFactor).toLong))
}
"be accessible through extension" in {
val settingsViaExtension = TestKitExtension(system)
val settingsViaApply = TestKitSettings(system)
settingsViaExtension should be theSameInstanceAs settingsViaApply
}
}
}Override Default Values:
// Custom configuration in application.conf or test configuration
val customConfig = ConfigFactory.parseString("""
akka.test {
timefactor = 2.0
single-expect-default = 5s
multi-expect-default = 10s
expect-no-message-default = 200ms
filter-leeway = 5s
default-timeout = 30s
}
""")
val system = ActorSystem("CustomTestSystem", customConfig)
class CustomConfigTest extends TestKit(system) {
"Custom configuration" should {
"use overridden values" in {
val settings = TestKitExtension(system)
settings.TestTimeFactor should equal(2.0)
settings.SingleExpectDefaultTimeout should equal(5.seconds)
settings.MultiExpectDefaultTimeout should equal(10.seconds)
settings.ExpectNoMessageDefaultTimeout should equal(200.millis)
settings.TestEventFilterLeeway should equal(5.seconds)
settings.DefaultTimeout.duration should equal(30.seconds)
}
}
}Environment-Specific Configuration:
// Different configurations for different environments
object TestConfigurations {
val ciConfig = ConfigFactory.parseString("""
akka.test {
timefactor = 3.0 # Slower CI environment
single-expect-default = 10s
filter-leeway = 10s
}
""")
val localConfig = ConfigFactory.parseString("""
akka.test {
timefactor = 1.0 # Fast local development
single-expect-default = 3s
filter-leeway = 3s
}
""")
val loadTestConfig = ConfigFactory.parseString("""
akka.test {
timefactor = 5.0 # Very slow load testing environment
single-expect-default = 30s
multi-expect-default = 60s
filter-leeway = 15s
}
""")
def getConfig(environment: String): Config = environment match {
case "ci" => ciConfig
case "local" => localConfig
case "load" => loadTestConfig
case _ => ConfigFactory.empty()
}
}
// Usage in tests
class EnvironmentConfigTest {
val environment = sys.env.getOrElse("TEST_ENV", "local")
val config = TestConfigurations.getConfig(environment)
val system = ActorSystem("EnvTestSystem", config)
// Tests will use environment-appropriate timeouts
}Automatic Timeout Usage:
class TimeoutIntegrationTest extends TestKit(ActorSystem("TestSystem")) with DefaultTimeout {
"TestKit with configuration" should {
"use configured timeouts automatically" in {
val actor = system.actorOf(Props(new Actor {
def receive = {
case msg =>
Thread.sleep(100) // Simulate work
sender() ! s"processed: $msg"
}
}))
// Uses SingleExpectDefaultTimeout from configuration
actor ! "test"
expectMsg("processed: test") // No explicit timeout needed
// Uses ExpectNoMessageDefaultTimeout from configuration
expectNoMessage() // Uses configured default
}
"respect time dilation" in {
import akka.testkit._
// All dilated timeouts use TestTimeFactor
within(2.seconds.dilated) {
// Code that might run slowly in some environments
Thread.sleep(100)
expectNoMessage(100.millis.dilated)
}
}
}
}Event Filter Configuration Integration:
class EventFilterConfigTest extends TestKit(ActorSystem("TestSystem")) {
"Event filters with configuration" should {
"use configured filter leeway" in {
val settings = TestKitExtension(system)
// EventFilter internally uses TestEventFilterLeeway for timeout
EventFilter.info(message = "test message").intercept {
// Simulate slow logging
Future {
Thread.sleep(settings.TestEventFilterLeeway.toMillis / 2)
log.info("test message")
}
// Filter waits up to filter-leeway time for the event
}
}
}
}Dynamic Configuration:
class DynamicConfigTest {
def createSystemWithTimeoutFactor(factor: Double): ActorSystem = {
val config = ConfigFactory.parseString(s"""
akka.test.timefactor = $factor
""")
ActorSystem("DynamicTestSystem", config)
}
"Dynamic configuration" should {
"allow runtime configuration changes" in {
val fastSystem = createSystemWithTimeoutFactor(1.0)
val slowSystem = createSystemWithTimeoutFactor(5.0)
val fastKit = new TestKit(fastSystem)
val slowKit = new TestKit(slowSystem)
import fastKit._
val fastSettings = TestKitExtension(fastSystem)
fastSettings.TestTimeFactor should equal(1.0)
import slowKit._
val slowSettings = TestKitExtension(slowSystem)
slowSettings.TestTimeFactor should equal(5.0)
fastSystem.terminate()
slowSystem.terminate()
}
}
}Configuration Validation:
class ConfigValidationTest extends TestKit(ActorSystem("TestSystem")) {
"Configuration validation" should {
"ensure reasonable timeout values" in {
val settings = TestKitExtension(system)
// Validate timeout values are reasonable
settings.SingleExpectDefaultTimeout should be >= 100.millis
settings.MultiExpectDefaultTimeout should be >= settings.SingleExpectDefaultTimeout
settings.TestEventFilterLeeway should be >= 1.second
settings.TestTimeFactor should be >= 1.0
// Validate dilated timeouts don't exceed reasonable bounds
val dilated = settings.dilated(1.minute)
dilated should be <= 10.minutes // Reasonable upper bound
}
"provide consistent configuration access" in {
// Multiple ways to access should return same instance
val settings1 = TestKitExtension(system)
val settings2 = TestKitSettings(system)
settings1 should be theSameInstanceAs settings2
settings1.TestTimeFactor should equal(settings2.TestTimeFactor)
}
}
}// Good: Environment-aware configuration
val timeFactor = if (sys.env.contains("CI")) 3.0 else 1.0
val config = ConfigFactory.parseString(s"akka.test.timefactor = $timeFactor")
// Good: Consistent settings access
val settings = TestKitExtension(system)
val timeout = settings.SingleExpectDefaultTimeout
// Good: Use dilated timeouts everywhere
expectMsg(5.seconds.dilated, expectedMessage)
within(10.seconds.dilated) { /* test code */ }
// Good: Validate configuration in tests
assert(settings.TestTimeFactor >= 1.0, "Time factor should be at least 1.0")
assert(settings.DefaultTimeout.duration > Duration.Zero, "Default timeout must be positive")Performance Issues:
// Problem: Time factor too high for local development
akka.test.timefactor = 10.0 // Makes tests unnecessarily slow locally
// Solution: Environment-specific factors
akka.test.timefactor = ${?TEST_TIME_FACTOR} // Override via environment variableTimeout Issues:
// Problem: Timeouts too short for slow operations
akka.test.single-expect-default = 100ms // Too short for most operations
// Solution: Reasonable defaults with dilation
akka.test.single-expect-default = 3s
// Then use .dilated in tests for environment adaptationFilter Issues:
// Problem: Filter leeway too short for async logging
akka.test.filter-leeway = 100ms // May miss async log events
// Solution: Adequate leeway for async operations
akka.test.filter-leeway = 3s