0
# Synchronous Actor Testing
1
2
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.
3
4
## Capabilities
5
6
### TestActorRef Class
7
8
Special ActorRef for synchronous testing with direct access to the underlying actor instance.
9
10
```scala { .api }
11
/**
12
* Special ActorRef exclusively for unit testing in single-threaded environment
13
* Overrides dispatcher to CallingThreadDispatcher for synchronous execution
14
* @param _system ActorSystem to use
15
* @param _props Props for creating the actor
16
* @param _supervisor Supervisor actor reference
17
* @param name Name for the test actor
18
*/
19
class TestActorRef[T <: Actor](_system: ActorSystem, _props: Props,
20
_supervisor: ActorRef, name: String)
21
extends LocalActorRef {
22
23
/**
24
* Direct access to underlying actor instance
25
* WARNING: Only use in single-threaded test environments
26
* @return The actor instance
27
*/
28
def underlyingActor: T
29
30
/**
31
* Directly inject message into actor's receive method
32
* @param o Message to inject
33
*/
34
def receive(o: Any): Unit
35
36
/**
37
* Directly inject message with specific sender
38
* @param o Message to inject
39
* @param sender Sender reference for the message
40
*/
41
def receive(o: Any, sender: ActorRef): Unit
42
43
/**
44
* Watch subject actor for termination
45
* @param subject Actor to watch
46
* @return The subject ActorRef
47
*/
48
def watch(subject: ActorRef): ActorRef
49
50
/**
51
* Stop watching subject actor
52
* @param subject Actor to unwatch
53
* @return The subject ActorRef
54
*/
55
def unwatch(subject: ActorRef): ActorRef
56
}
57
```
58
59
### Factory Methods
60
61
Factory methods for creating TestActorRef instances with various parameter combinations.
62
63
```scala { .api }
64
object TestActorRef {
65
/**
66
* Create TestActorRef from actor factory function
67
* @param factory Function that creates actor instance
68
* @param system Implicit ActorSystem
69
* @return TestActorRef for the created actor
70
*/
71
def apply[T <: Actor: ClassTag](factory: => T)
72
(implicit system: ActorSystem): TestActorRef[T]
73
74
/**
75
* Create named TestActorRef from actor factory function
76
* @param factory Function that creates actor instance
77
* @param name Name for the actor
78
* @param system Implicit ActorSystem
79
* @return TestActorRef for the created actor
80
*/
81
def apply[T <: Actor: ClassTag](factory: => T, name: String)
82
(implicit system: ActorSystem): TestActorRef[T]
83
84
/**
85
* Create TestActorRef from Props
86
* @param props Props for creating the actor
87
* @param system Implicit ActorSystem
88
* @return TestActorRef for the created actor
89
*/
90
def apply[T <: Actor](props: Props)
91
(implicit system: ActorSystem): TestActorRef[T]
92
93
/**
94
* Create named TestActorRef from Props
95
* @param props Props for creating the actor
96
* @param name Name for the actor
97
* @param system Implicit ActorSystem
98
* @return TestActorRef for the created actor
99
*/
100
def apply[T <: Actor](props: Props, name: String)
101
(implicit system: ActorSystem): TestActorRef[T]
102
103
/**
104
* Create TestActorRef with custom supervisor from Props
105
* @param props Props for creating the actor
106
* @param supervisor Supervisor actor reference
107
* @param name Name for the actor
108
* @param system Implicit ActorSystem
109
* @return TestActorRef for the created actor
110
*/
111
def apply[T <: Actor](props: Props, supervisor: ActorRef, name: String)
112
(implicit system: ActorSystem): TestActorRef[T]
113
}
114
```
115
116
### Java API
117
118
Java-friendly factory methods for creating TestActorRef instances.
119
120
```scala { .api }
121
object TestActorRef {
122
/**
123
* Java API: Create TestActorRef from Props
124
* @param system ActorSystem to use
125
* @param props Props for creating the actor
126
* @return TestActorRef for the created actor
127
*/
128
def create[T <: Actor](system: ActorSystem, props: Props): TestActorRef[T]
129
130
/**
131
* Java API: Create named TestActorRef from Props
132
* @param system ActorSystem to use
133
* @param props Props for creating the actor
134
* @param name Name for the actor
135
* @return TestActorRef for the created actor
136
*/
137
def create[T <: Actor](system: ActorSystem, props: Props,
138
name: String): TestActorRef[T]
139
140
/**
141
* Java API: Create TestActorRef with custom supervisor
142
* @param system ActorSystem to use
143
* @param props Props for creating the actor
144
* @param supervisor Supervisor actor reference
145
* @param name Name for the actor
146
* @return TestActorRef for the created actor
147
*/
148
def create[T <: Actor](system: ActorSystem, props: Props,
149
supervisor: ActorRef, name: String): TestActorRef[T]
150
}
151
```
152
153
**Usage Examples:**
154
155
```scala
156
import akka.actor.{Actor, ActorSystem, Props}
157
import akka.testkit.TestActorRef
158
159
class CounterActor extends Actor {
160
private var count = 0
161
162
def receive = {
163
case "increment" => count += 1
164
case "get" => sender() ! count
165
case "reset" => count = 0
166
}
167
168
def getCount: Int = count
169
}
170
171
class TestActorRefExample {
172
implicit val system = ActorSystem("test")
173
174
// Create TestActorRef from Props
175
val counterRef = TestActorRef[CounterActor](Props[CounterActor])
176
177
// Direct access to actor instance
178
val counter = counterRef.underlyingActor
179
180
// Synchronous message injection
181
counterRef.receive("increment")
182
counterRef.receive("increment")
183
184
// Direct state inspection
185
assert(counter.getCount == 2)
186
187
// Test with specific sender
188
val probe = TestProbe()
189
counterRef.receive("get", probe.ref)
190
probe.expectMsg(2)
191
192
// Factory method with name
193
val namedRef = TestActorRef[CounterActor](Props[CounterActor], "my-counter")
194
195
// Factory method with actor factory
196
val factoryRef = TestActorRef(new CounterActor)
197
}
198
```
199
200
### Integration with Regular ActorRef
201
202
TestActorRef extends ActorRef so it can be used anywhere a regular ActorRef is expected, but provides additional synchronous testing capabilities.
203
204
```scala
205
// TestActorRef can be used as regular ActorRef
206
val testRef: ActorRef = TestActorRef[CounterActor](Props[CounterActor])
207
208
// Send messages asynchronously (normal actor behavior)
209
testRef ! "increment"
210
211
// But also supports synchronous testing
212
val testActorRef = testRef.asInstanceOf[TestActorRef[CounterActor]]
213
testActorRef.receive("increment") // Synchronous
214
val actor = testActorRef.underlyingActor // Direct access
215
```
216
217
### Thread Safety Considerations
218
219
TestActorRef is designed for single-threaded testing environments and should not be used in multi-threaded scenarios.
220
221
```scala
222
// SAFE: Single-threaded test
223
class SingleThreadedTest extends TestKit(ActorSystem("test")) {
224
val ref = TestActorRef[MyActor](Props[MyActor])
225
ref.receive("message")
226
val state = ref.underlyingActor.someState
227
}
228
229
// UNSAFE: Multi-threaded access
230
// Don't access underlyingActor from multiple threads
231
// Don't mix asynchronous ! with synchronous receive()
232
```
233
234
### Supervision and Lifecycle
235
236
TestActorRef supports standard actor lifecycle events and supervision, but executes them synchronously.
237
238
```scala
239
class SupervisedActor extends Actor {
240
def receive = {
241
case "fail" => throw new RuntimeException("Test failure")
242
case msg => // handle normally
243
}
244
245
override def preStart(): Unit = println("Starting")
246
override def postStop(): Unit = println("Stopping")
247
}
248
249
// Supervision works synchronously
250
val ref = TestActorRef[SupervisedActor](Props[SupervisedActor])
251
252
// Lifecycle methods are called synchronously
253
// preStart() already called during construction
254
255
// Exception handling is synchronous
256
try {
257
ref.receive("fail")
258
} catch {
259
case _: RuntimeException => // Handle test exception
260
}
261
```