or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

actor-refs.mdconfiguration.mdcore-testing.mddispatchers.mdevent-filtering.mdindex.mdjava-dsl.mdpackage-functions.mdsynchronization.mdtest-actors.mdutilities.md
tile.json

test-actors.mddocs/

Test Actors and Utilities

Pre-built actor implementations and factory methods for common testing patterns, including echo actors, blackhole actors, and message forwarding actors.

Capabilities

TestActors Object

Collection of common test actor patterns and their factory methods.

object TestActors {
  // Pre-built actor classes
  class EchoActor extends Actor
  class BlackholeActor extends Actor  
  class ForwardActor(target: ActorRef) extends Actor
  
  // Factory Props
  val echoActorProps: Props
  val blackholeProps: Props
  def forwardActorProps(target: ActorRef): Props
}

EchoActor

Actor that echoes back all received messages to the sender.

class EchoActor extends Actor {
  def receive = {
    case message => sender() ! message
  }
}

Usage Example:

import akka.testkit.TestActors

class EchoActorTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
  "EchoActor" should {
    "echo back messages" in {
      val echo = system.actorOf(TestActors.echoActorProps)
      
      echo ! "hello"
      expectMsg("hello")
      
      echo ! 42
      expectMsg(42)
      
      echo ! List(1, 2, 3)
      expectMsg(List(1, 2, 3))
    }
    
    "preserve sender reference" in {
      val echo = system.actorOf(TestActors.echoActorProps)
      val probe = TestProbe()
      
      probe.send(echo, "test")
      probe.expectMsg("test")
    }
  }
}

BlackholeActor

Actor that ignores all messages (useful for dead-ending message flows).

class BlackholeActor extends Actor {
  def receive = {
    case _ => // Ignore all messages
  }
}

Usage Example:

class BlackholeActorTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
  "BlackholeActor" should {
    "ignore all messages" in {
      val blackhole = system.actorOf(TestActors.blackholeProps)
      
      blackhole ! "message1"
      blackhole ! "message2"
      blackhole ! 123
      
      expectNoMessage(500.millis)
    }
    
    "be useful for message routing tests" in {
      val blackhole = system.actorOf(TestActors.blackholeProps)
      
      // Test that messages are routed to blackhole
      val router = system.actorOf(Props(new Router(blackhole)))
      router ! RouteToBlackhole("unwanted message")
      
      expectNoMessage(200.millis) // Message was consumed by blackhole
    }
  }
}

ForwardActor

Actor that forwards all messages to a specified target actor.

class ForwardActor(target: ActorRef) extends Actor {
  def receive = {
    case message => target.forward(message)
  }
}

Usage Example:

class ForwardActorTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
  "ForwardActor" should {
    "forward messages to target" in {
      val target = TestProbe()
      val forwarder = system.actorOf(TestActors.forwardActorProps(target.ref))
      
      forwarder ! "hello"
      target.expectMsg("hello")
      
      forwarder ! 42
      target.expectMsg(42)
    }
    
    "preserve original sender" in {
      val target = TestProbe()
      val forwarder = system.actorOf(TestActors.forwardActorProps(target.ref))
      val originalSender = TestProbe()
      
      originalSender.send(forwarder, "test message")
      
      val message = target.expectMsgType[String]
      assert(message == "test message")
      target.lastSender should be(originalSender.ref)
    }
    
    "be useful for proxy patterns" in {
      val actualService = TestProbe()
      val proxy = system.actorOf(TestActors.forwardActorProps(actualService.ref))
      
      // Client thinks it's talking to the service directly
      proxy ! ServiceRequest("data")
      actualService.expectMsg(ServiceRequest("data"))
      
      // Service responds back through proxy
      actualService.reply(ServiceResponse("result"))
      expectMsg(ServiceResponse("result"))
    }
  }
}

Common Usage Patterns

Testing Message Flows:

class MessageFlowTest extends TestKit(ActorSystem("TestSystem")) with ImplicitSender {
  "Message flow testing" should {
    "verify routing with test actors" in {
      val echo = system.actorOf(TestActors.echoActorProps, "echo")
      val blackhole = system.actorOf(TestActors.blackholeProps, "blackhole")
      val probe = TestProbe("target")
      val forwarder = system.actorOf(TestActors.forwardActorProps(probe.ref), "forwarder")
      
      // Create a router that routes based on message type
      val router = system.actorOf(Props(new MessageRouter(
        Map(
          "echo" -> echo,
          "ignore" -> blackhole,  
          "forward" -> forwarder
        )
      )))
      
      // Test echo routing
      router ! RouteMessage("echo", "hello")
      expectMsg("hello")
      
      // Test blackhole routing
      router ! RouteMessage("ignore", "unwanted")
      expectNoMessage(100.millis)
      
      // Test forward routing
      router ! RouteMessage("forward", "forwarded")
      probe.expectMsg("forwarded")
    }
  }
}

Load Testing with Test Actors:

class LoadTest extends TestKit(ActorSystem("TestSystem")) {
  "Load testing" should {
    "handle high message volume" in {
      val echo = system.actorOf(TestActors.echoActorProps)
      val messageCount = 1000
      
      (1 to messageCount).foreach { i =>
        echo ! s"message-$i"
      }
      
      val responses = receiveN(messageCount, 10.seconds)
      responses should have size messageCount
      responses.zipWithIndex.foreach { case (msg, i) =>
        msg should be(s"message-${i + 1}")
      }
    }
  }
}

Integration Testing:

class IntegrationTest extends TestKit(ActorSystem("TestSystem")) {
  "Integration with test actors" should {
    "simulate external dependencies" in {
      // Use ForwardActor to simulate external service
      val externalServiceMock = TestProbe("external-service")
      val serviceProxy = system.actorOf(
        TestActors.forwardActorProps(externalServiceMock.ref),
        "service-proxy"
      )
      
      // System under test uses the proxy
      val businessLogic = system.actorOf(Props(new BusinessLogic(serviceProxy)))
      
      businessLogic ! ProcessData("test-data")
      
      // Verify external service call
      externalServiceMock.expectMsg(ExternalServiceCall("test-data"))
      
      // Mock response
      externalServiceMock.reply(ExternalServiceResponse("processed"))
      
      // Verify business logic processes response
      expectMsg(DataProcessed("processed"))
    }
  }
}

Custom Test Actors

You can create custom test actors for specific testing scenarios:

// Custom test actor that counts messages
class CountingActor extends Actor {
  private var count = 0
  
  def receive = {
    case "count" => 
      sender() ! count
    case _ => 
      count += 1
  }
}

// Custom test actor that delays responses
class DelayedEchoActor(delay: FiniteDuration) extends Actor {
  import context.dispatcher
  
  def receive = {
    case message =>
      val originalSender = sender()
      context.system.scheduler.scheduleOnce(delay) {
        originalSender ! message
      }
  }
}

// Custom test actor factory
object CustomTestActors {
  def countingActorProps: Props = Props[CountingActor]()
  def delayedEchoProps(delay: FiniteDuration): Props = Props(new DelayedEchoActor(delay))
}

Best Practices

  1. Use Appropriate Test Actors:

    • EchoActor for simple request-response testing
    • BlackholeActor for testing message routing and filtering
    • ForwardActor for proxy and integration testing
  2. Combine with TestProbes: Test actors work well with TestProbe for complex scenarios

  3. Name Your Actors: Use descriptive names for easier debugging

  4. Clean Resource Usage: Test actors are lightweight but still consume resources

// Good: Descriptive names and appropriate usage
val echoService = system.actorOf(TestActors.echoActorProps, "echo-service")
val messageSync = system.actorOf(TestActors.blackholeProps, "message-sink") 
val serviceProxy = system.actorOf(TestActors.forwardActorProps(realService), "service-proxy")

// Good: Combine with probes for verification
val probe = TestProbe("verification-probe")
val forwarder = system.actorOf(TestActors.forwardActorProps(probe.ref))