Global utility functions and implicit classes available at the package level for common testing operations and time dilation.
Package-level functions for convenient event filtering operations.
package object testkit {
// Event filtering with multiple filters
def filterEvents[T](eventFilters: Iterable[EventFilter])(block: => T)(implicit system: ActorSystem): T
def filterEvents[T](eventFilters: EventFilter*)(block: => T)(implicit system: ActorSystem): T
// Exception filtering shorthand
def filterException[T <: Throwable](block: => Unit)(implicit system: ActorSystem, t: ClassTag[T]): Unit
}Usage Examples:
import akka.testkit._
class PackageFunctionsTest extends TestKit(ActorSystem("TestSystem")) {
"Package-level filterEvents" should {
"filter multiple event types" in {
filterEvents(
EventFilter.error(occurrences = 1),
EventFilter.warning(pattern = "deprecated".r, occurrences = 2),
EventFilter[RuntimeException](occurrences = 1)
) {
val actor = system.actorOf(Props(new Actor {
def receive = {
case "error" => log.error("Test error")
case "warn" =>
log.warning("Use of deprecated feature")
log.warning("Another deprecated warning")
case "exception" => throw new RuntimeException("Test exception")
}
}))
actor ! "error"
actor ! "warn"
actor ! "exception"
}
}
"work with iterable filters" in {
val filters = List(
EventFilter.info(message = "Starting", occurrences = 1),
EventFilter.info(message = "Completed", occurrences = 1)
)
filterEvents(filters) {
val actor = system.actorOf(Props(new Actor {
def receive = {
case "process" =>
log.info("Starting")
// Do work
log.info("Completed")
}
}))
actor ! "process"
}
}
}
"Package-level filterException" should {
"provide shorthand for exception filtering" in {
filterException[IllegalArgumentException] {
val actor = system.actorOf(Props(new Actor {
def receive = {
case invalid: String if invalid.isEmpty =>
throw new IllegalArgumentException("Empty string not allowed")
}
}))
actor ! ""
}
}
"work with type inference" in {
import scala.reflect.ClassTag
def testException[E <: Throwable : ClassTag](triggerException: => Unit): Unit = {
filterException[E] {
triggerException
}
}
testException[NullPointerException] {
throw new NullPointerException("Test NPE")
}
}
}
}Implicit class for scaling durations based on test environment.
implicit class TestDuration(val duration: FiniteDuration) extends AnyVal {
def dilated(implicit system: ActorSystem): FiniteDuration
}Usage Examples:
import akka.testkit._
import scala.concurrent.duration._
class TimeDilationTest extends TestKit(ActorSystem("TestSystem")) {
"TestDuration implicit class" should {
"dilate durations for test reliability" in {
val originalTimeout = 1.second
val dilatedTimeout = originalTimeout.dilated
// Dilated timeout accounts for slow test environments
dilatedTimeout should be >= originalTimeout
// Use in expectations
within(5.seconds.dilated) {
// Code that might run slowly in CI
Thread.sleep(100)
expectNoMessage(500.millis.dilated)
}
}
"work with various duration types" in {
// All duration types work
val millis = 100.millis.dilated
val seconds = 2.seconds.dilated
val minutes = 1.minute.dilated
millis should be >= 100.millis
seconds should be >= 2.seconds
minutes should be >= 1.minute
}
"respect test time factor configuration" in {
// Time factor configured in application.conf: akka.test.timefactor = 2.0
val original = 1.second
val dilated = original.dilated
// With timefactor = 2.0, dilated should be ~2x original
val settings = TestKitExtension(system)
val expectedDilated = Duration.fromNanos((original.toNanos * settings.TestTimeFactor).toLong)
dilated should be(expectedDilated)
}
"be used in actor timing tests" in {
class TimedActor extends Actor {
import context.dispatcher
def receive = {
case "delayed-response" =>
val originalSender = sender()
context.system.scheduler.scheduleOnce(200.millis.dilated) {
originalSender ! "response"
}
}
}
val actor = system.actorOf(Props[TimedActor]())
actor ! "delayed-response"
// Wait with dilated timeout to account for test environment
expectMsg(1.second.dilated, "response")
}
}
}Package-level functions integrate seamlessly with TestKit methods:
class IntegrationTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
"Package functions with TestKit" should {
"combine with expectation methods" in {
val actor = system.actorOf(Props(new Actor {
def receive = {
case "test" =>
log.info("Processing test message")
sender() ! "processed"
}
}))
filterEvents(EventFilter.info(message = "Processing test message")) {
actor ! "test"
expectMsg(3.seconds.dilated, "processed")
}
}
"work with awaitCond" in {
@volatile var completed = false
val actor = system.actorOf(Props(new Actor {
def receive = {
case "complete" =>
Thread.sleep(100) // Simulate work
completed = true
}
}))
actor ! "complete"
awaitCond(completed, max = 2.seconds.dilated)
assert(completed)
}
"combine with within blocks" in {
within(1.second.dilated) {
filterEvents(EventFilter.debug(start = "Debug message")) {
val actor = system.actorOf(Props(new Actor {
def receive = {
case msg =>
log.debug(s"Debug message: $msg")
sender() ! s"handled: $msg"
}
}))
actor ! "test"
expectMsg("handled: test")
}
}
}
}
}Nested Event Filtering:
class NestedFilteringTest extends TestKit(ActorSystem("TestSystem")) {
"Nested event filtering" should {
"work with multiple layers" in {
filterEvents(EventFilter.error(occurrences = 1)) {
filterEvents(EventFilter.warning(occurrences = 2)) {
val actor = system.actorOf(Props(new Actor {
def receive = {
case "process" =>
log.warning("First warning")
log.warning("Second warning")
log.error("An error occurred")
}
}))
actor ! "process"
}
}
}
}
}Dynamic Filter Creation:
class DynamicFilterTest extends TestKit(ActorSystem("TestSystem")) {
"Dynamic filter creation" should {
"create filters based on test parameters" in {
def testWithFilters(errorCount: Int, warningCount: Int): Unit = {
val filters = Seq(
if (errorCount > 0) Some(EventFilter.error(occurrences = errorCount)) else None,
if (warningCount > 0) Some(EventFilter.warning(occurrences = warningCount)) else None
).flatten
filterEvents(filters) {
val actor = system.actorOf(Props(new Actor {
def receive = {
case (errors: Int, warnings: Int) =>
(1 to errors).foreach(_ => log.error("Error"))
(1 to warnings).foreach(_ => log.warning("Warning"))
}
}))
actor ! (errorCount, warningCount)
}
}
testWithFilters(2, 3)
testWithFilters(0, 1)
testWithFilters(1, 0)
}
}
}Package functions respect TestKit configuration:
# application.conf
akka {
test {
# Time dilation factor - all dilated durations multiplied by this
timefactor = 1.0
# Event filter leeway - extra time to wait for expected log events
filter-leeway = 3s
# Default timeouts
single-expect-default = 3s
multi-expect-default = 3s
}
}Accessing Configuration:
class ConfigurationAccessTest extends TestKit(ActorSystem("TestSystem")) {
"Configuration access" should {
"use configured time factors" in {
val settings = TestKitExtension(system)
val timeFactor = settings.TestTimeFactor
val original = 1.second
val dilated = original.dilated
val expected = Duration.fromNanos((original.toNanos * timeFactor).toLong)
dilated should be(expected)
}
}
}.dilated for all test timeouts to handle slow environments// Good: Dilated timeouts for reliability
expectMsg(5.seconds.dilated, expectedMessage)
awaitCond(condition, max = 3.seconds.dilated)
// Good: Specific event filtering
filterEvents(
EventFilter.error(source = "com.myapp.Service", occurrences = 1),
EventFilter.warning(pattern = "deprecated.*method".r, occurrences = 1)
) {
// test code
}
// Good: Combined with TestKit methods
within(10.seconds.dilated) {
filterException[IllegalStateException] {
actor ! InvalidCommand()
}
}