TestActorRef provides synchronous access to actor internals for unit testing individual actors without the asynchronous nature of the actor model. This enables direct testing of actor state and behavior through synchronous method calls.
Special ActorRef for synchronous testing with direct access to the underlying actor instance.
/**
* Special ActorRef exclusively for unit testing in single-threaded environment
* Overrides dispatcher to CallingThreadDispatcher for synchronous execution
* @param _system ActorSystem to use
* @param _props Props for creating the actor
* @param _supervisor Supervisor actor reference
* @param name Name for the test actor
*/
class TestActorRef[T <: Actor](_system: ActorSystem, _props: Props,
_supervisor: ActorRef, name: String)
extends LocalActorRef {
/**
* Direct access to underlying actor instance
* WARNING: Only use in single-threaded test environments
* @return The actor instance
*/
def underlyingActor: T
/**
* Directly inject message into actor's receive method
* @param o Message to inject
*/
def receive(o: Any): Unit
/**
* Directly inject message with specific sender
* @param o Message to inject
* @param sender Sender reference for the message
*/
def receive(o: Any, sender: ActorRef): Unit
/**
* Watch subject actor for termination
* @param subject Actor to watch
* @return The subject ActorRef
*/
def watch(subject: ActorRef): ActorRef
/**
* Stop watching subject actor
* @param subject Actor to unwatch
* @return The subject ActorRef
*/
def unwatch(subject: ActorRef): ActorRef
}Factory methods for creating TestActorRef instances with various parameter combinations.
object TestActorRef {
/**
* Create TestActorRef from actor factory function
* @param factory Function that creates actor instance
* @param system Implicit ActorSystem
* @return TestActorRef for the created actor
*/
def apply[T <: Actor: ClassTag](factory: => T)
(implicit system: ActorSystem): TestActorRef[T]
/**
* Create named TestActorRef from actor factory function
* @param factory Function that creates actor instance
* @param name Name for the actor
* @param system Implicit ActorSystem
* @return TestActorRef for the created actor
*/
def apply[T <: Actor: ClassTag](factory: => T, name: String)
(implicit system: ActorSystem): TestActorRef[T]
/**
* Create TestActorRef from Props
* @param props Props for creating the actor
* @param system Implicit ActorSystem
* @return TestActorRef for the created actor
*/
def apply[T <: Actor](props: Props)
(implicit system: ActorSystem): TestActorRef[T]
/**
* Create named TestActorRef from Props
* @param props Props for creating the actor
* @param name Name for the actor
* @param system Implicit ActorSystem
* @return TestActorRef for the created actor
*/
def apply[T <: Actor](props: Props, name: String)
(implicit system: ActorSystem): TestActorRef[T]
/**
* Create TestActorRef with custom supervisor from Props
* @param props Props for creating the actor
* @param supervisor Supervisor actor reference
* @param name Name for the actor
* @param system Implicit ActorSystem
* @return TestActorRef for the created actor
*/
def apply[T <: Actor](props: Props, supervisor: ActorRef, name: String)
(implicit system: ActorSystem): TestActorRef[T]
}Java-friendly factory methods for creating TestActorRef instances.
object TestActorRef {
/**
* Java API: Create TestActorRef from Props
* @param system ActorSystem to use
* @param props Props for creating the actor
* @return TestActorRef for the created actor
*/
def create[T <: Actor](system: ActorSystem, props: Props): TestActorRef[T]
/**
* Java API: Create named TestActorRef from Props
* @param system ActorSystem to use
* @param props Props for creating the actor
* @param name Name for the actor
* @return TestActorRef for the created actor
*/
def create[T <: Actor](system: ActorSystem, props: Props,
name: String): TestActorRef[T]
/**
* Java API: Create TestActorRef with custom supervisor
* @param system ActorSystem to use
* @param props Props for creating the actor
* @param supervisor Supervisor actor reference
* @param name Name for the actor
* @return TestActorRef for the created actor
*/
def create[T <: Actor](system: ActorSystem, props: Props,
supervisor: ActorRef, name: String): TestActorRef[T]
}Usage Examples:
import akka.actor.{Actor, ActorSystem, Props}
import akka.testkit.TestActorRef
class CounterActor extends Actor {
private var count = 0
def receive = {
case "increment" => count += 1
case "get" => sender() ! count
case "reset" => count = 0
}
def getCount: Int = count
}
class TestActorRefExample {
implicit val system = ActorSystem("test")
// Create TestActorRef from Props
val counterRef = TestActorRef[CounterActor](Props[CounterActor])
// Direct access to actor instance
val counter = counterRef.underlyingActor
// Synchronous message injection
counterRef.receive("increment")
counterRef.receive("increment")
// Direct state inspection
assert(counter.getCount == 2)
// Test with specific sender
val probe = TestProbe()
counterRef.receive("get", probe.ref)
probe.expectMsg(2)
// Factory method with name
val namedRef = TestActorRef[CounterActor](Props[CounterActor], "my-counter")
// Factory method with actor factory
val factoryRef = TestActorRef(new CounterActor)
}TestActorRef extends ActorRef so it can be used anywhere a regular ActorRef is expected, but provides additional synchronous testing capabilities.
// TestActorRef can be used as regular ActorRef
val testRef: ActorRef = TestActorRef[CounterActor](Props[CounterActor])
// Send messages asynchronously (normal actor behavior)
testRef ! "increment"
// But also supports synchronous testing
val testActorRef = testRef.asInstanceOf[TestActorRef[CounterActor]]
testActorRef.receive("increment") // Synchronous
val actor = testActorRef.underlyingActor // Direct accessTestActorRef is designed for single-threaded testing environments and should not be used in multi-threaded scenarios.
// SAFE: Single-threaded test
class SingleThreadedTest extends TestKit(ActorSystem("test")) {
val ref = TestActorRef[MyActor](Props[MyActor])
ref.receive("message")
val state = ref.underlyingActor.someState
}
// UNSAFE: Multi-threaded access
// Don't access underlyingActor from multiple threads
// Don't mix asynchronous ! with synchronous receive()TestActorRef supports standard actor lifecycle events and supervision, but executes them synchronously.
class SupervisedActor extends Actor {
def receive = {
case "fail" => throw new RuntimeException("Test failure")
case msg => // handle normally
}
override def preStart(): Unit = println("Starting")
override def postStop(): Unit = println("Stopping")
}
// Supervision works synchronously
val ref = TestActorRef[SupervisedActor](Props[SupervisedActor])
// Lifecycle methods are called synchronously
// preStart() already called during construction
// Exception handling is synchronous
try {
ref.receive("fail")
} catch {
case _: RuntimeException => // Handle test exception
}