0
# FSM Testing
1
2
TestFSMRef provides specialized testing utilities for Finite State Machine actors, extending TestActorRef with FSM-specific functionality including state inspection, state manipulation, and timer management for comprehensive FSM testing.
3
4
## Capabilities
5
6
### TestFSMRef Class
7
8
Specialized TestActorRef for testing FSM (Finite State Machine) actors with state inspection and timer management.
9
10
```scala { .api }
11
/**
12
* Specialized TestActorRef for FSM actors
13
* Provides direct access to FSM state and timer management
14
* @param system ActorSystem to use
15
* @param props Props for creating the FSM actor
16
* @param supervisor Supervisor actor reference
17
* @param name Name for the test actor
18
*/
19
class TestFSMRef[S, D, T <: Actor](system: ActorSystem, props: Props,
20
supervisor: ActorRef, name: String)
21
extends TestActorRef[T] {
22
23
/**
24
* Get current FSM state name
25
* @return Current state name of type S
26
*/
27
def stateName: S
28
29
/**
30
* Get current FSM state data
31
* @return Current state data of type D
32
*/
33
def stateData: D
34
35
/**
36
* Manually set FSM state (for testing state transitions)
37
* @param stateName New state name
38
* @param stateData New state data
39
*/
40
def setState(stateName: S, stateData: D): Unit
41
42
/**
43
* Set FSM state with timeout and stop reason
44
* @param stateName New state name
45
* @param stateData New state data
46
* @param timeout State timeout duration
47
* @param stopReason Optional stop reason
48
*/
49
def setState(stateName: S, stateData: D, timeout: FiniteDuration,
50
stopReason: Option[FSM.Reason]): Unit
51
}
52
```
53
54
### Timer Management
55
56
Methods for managing FSM timers including starting, canceling, and checking timer status.
57
58
```scala { .api }
59
/**
60
* Start timer with fixed delay between executions
61
* @param name Timer name identifier
62
* @param msg Message to send when timer fires
63
* @param delay Delay between timer executions
64
*/
65
def startTimerWithFixedDelay(name: String, msg: Any, delay: FiniteDuration): Unit
66
67
/**
68
* Start timer with fixed rate execution
69
* @param name Timer name identifier
70
* @param msg Message to send when timer fires
71
* @param interval Fixed interval between timer executions
72
*/
73
def startTimerAtFixedRate(name: String, msg: Any, interval: FiniteDuration): Unit
74
75
/**
76
* Start single-shot timer
77
* @param name Timer name identifier
78
* @param msg Message to send when timer fires
79
* @param delay Delay before timer fires once
80
*/
81
def startSingleTimer(name: String, msg: Any, delay: FiniteDuration): Unit
82
83
/**
84
* Cancel named timer
85
* @param name Timer name to cancel
86
*/
87
def cancelTimer(name: String): Unit
88
89
/**
90
* Check if named timer is active
91
* @param name Timer name to check
92
* @return True if timer is active, false otherwise
93
*/
94
def isTimerActive(name: String): Boolean
95
96
/**
97
* Check if state timeout timer is active
98
* @return True if state timeout timer is active
99
*/
100
def isStateTimerActive: Boolean
101
```
102
103
### Factory Methods
104
105
Factory methods for creating TestFSMRef instances with various parameter combinations.
106
107
```scala { .api }
108
object TestFSMRef {
109
/**
110
* Create TestFSMRef from actor factory function
111
* @param factory Function that creates FSM actor instance
112
* @param ev Evidence that T is an FSM[S, D]
113
* @param system Implicit ActorSystem
114
* @return TestFSMRef for the created FSM actor
115
*/
116
def apply[S, D, T <: Actor: ClassTag](factory: => T)
117
(implicit ev: T <:< FSM[S, D],
118
system: ActorSystem): TestFSMRef[S, D, T]
119
120
/**
121
* Create named TestFSMRef from actor factory function
122
* @param factory Function that creates FSM actor instance
123
* @param name Name for the actor
124
* @param ev Evidence that T is an FSM[S, D]
125
* @param system Implicit ActorSystem
126
* @return TestFSMRef for the created FSM actor
127
*/
128
def apply[S, D, T <: Actor: ClassTag](factory: => T, name: String)
129
(implicit ev: T <:< FSM[S, D],
130
system: ActorSystem): TestFSMRef[S, D, T]
131
132
/**
133
* Create TestFSMRef from Props
134
* @param props Props for creating the FSM actor
135
* @param ev Evidence that T is an FSM[S, D]
136
* @param system Implicit ActorSystem
137
* @return TestFSMRef for the created FSM actor
138
*/
139
def apply[S, D, T <: Actor](props: Props)
140
(implicit ev: T <:< FSM[S, D],
141
system: ActorSystem): TestFSMRef[S, D, T]
142
143
/**
144
* Create named TestFSMRef from Props
145
* @param props Props for creating the FSM actor
146
* @param name Name for the actor
147
* @param supervisor Optional supervisor reference
148
* @param ev Evidence that T is an FSM[S, D]
149
* @param system Implicit ActorSystem
150
* @return TestFSMRef for the created FSM actor
151
*/
152
def apply[S, D, T <: Actor](props: Props, name: String,
153
supervisor: Option[ActorRef] = None)
154
(implicit ev: T <:< FSM[S, D],
155
system: ActorSystem): TestFSMRef[S, D, T]
156
}
157
```
158
159
**Usage Examples:**
160
161
```scala
162
import akka.actor.{ActorSystem, FSM, Props}
163
import akka.testkit.{TestFSMRef, TestKit}
164
import scala.concurrent.duration._
165
166
// Example FSM states and data
167
sealed trait State
168
case object Idle extends State
169
case object Active extends State
170
case object Completed extends State
171
172
case class Data(count: Int, items: List[String])
173
174
// Example FSM Actor
175
class ProcessorFSM extends FSM[State, Data] {
176
startWith(Idle, Data(0, Nil))
177
178
when(Idle) {
179
case Event("start", data) =>
180
goto(Active) using data.copy(count = 1)
181
}
182
183
when(Active, stateTimeout = 5.seconds) {
184
case Event("process", data) =>
185
val newData = data.copy(count = data.count + 1)
186
if (newData.count >= 10) {
187
goto(Completed) using newData
188
} else {
189
stay() using newData
190
}
191
case Event(StateTimeout, data) =>
192
goto(Idle) using data.copy(count = 0)
193
}
194
195
when(Completed) {
196
case Event("reset", _) =>
197
goto(Idle) using Data(0, Nil)
198
}
199
}
200
201
class FSMTestExample extends TestKit(ActorSystem("test")) {
202
203
"ProcessorFSM" should {
204
"start in Idle state" in {
205
val fsmRef = TestFSMRef(new ProcessorFSM)
206
207
// Check initial state
208
assert(fsmRef.stateName == Idle)
209
assert(fsmRef.stateData == Data(0, Nil))
210
}
211
212
"transition from Idle to Active" in {
213
val fsmRef = TestFSMRef(new ProcessorFSM)
214
215
// Send transition message
216
fsmRef ! "start"
217
218
// Verify state change
219
assert(fsmRef.stateName == Active)
220
assert(fsmRef.stateData.count == 1)
221
}
222
223
"handle state timeout" in {
224
val fsmRef = TestFSMRef(new ProcessorFSM)
225
226
// Manually set state to Active
227
fsmRef.setState(Active, Data(5, Nil))
228
229
// Check state timeout timer
230
assert(fsmRef.isStateTimerActive)
231
232
// Trigger state timeout
233
fsmRef ! FSM.StateTimeout
234
235
// Verify transition back to Idle
236
assert(fsmRef.stateName == Idle)
237
assert(fsmRef.stateData.count == 0)
238
}
239
240
"manage custom timers" in {
241
val fsmRef = TestFSMRef(new ProcessorFSM)
242
243
// Start a custom timer
244
fsmRef.startSingleTimer("reminder", "remind", 1.second)
245
246
// Check timer status
247
assert(fsmRef.isTimerActive("reminder"))
248
249
// Cancel timer
250
fsmRef.cancelTimer("reminder")
251
assert(!fsmRef.isTimerActive("reminder"))
252
}
253
254
"support manual state manipulation" in {
255
val fsmRef = TestFSMRef(new ProcessorFSM)
256
257
// Manually set complex state for testing
258
val testData = Data(8, List("item1", "item2"))
259
fsmRef.setState(Active, testData)
260
261
// Verify manual state setting
262
assert(fsmRef.stateName == Active)
263
assert(fsmRef.stateData == testData)
264
265
// Continue with normal FSM behavior
266
fsmRef ! "process"
267
fsmRef ! "process"
268
269
// Should transition to Completed (count reaches 10)
270
assert(fsmRef.stateName == Completed)
271
assert(fsmRef.stateData.count == 10)
272
}
273
}
274
}
275
```
276
277
### FSM Integration Features
278
279
TestFSMRef integrates with Akka FSM features for comprehensive testing.
280
281
```scala
282
// Testing FSM with complex data transformations
283
class DataProcessorFSM extends FSM[ProcessState, ProcessData] {
284
when(Processing) {
285
case Event(ProcessItem(item), data) =>
286
val newData = data.addItem(item)
287
if (newData.isComplete) {
288
goto(Complete) using newData forMax 30.seconds
289
} else {
290
stay() using newData
291
}
292
}
293
}
294
295
// Test complex FSM scenarios
296
val fsmRef = TestFSMRef(new DataProcessorFSM)
297
298
// Set up specific test scenario
299
fsmRef.setState(Processing, ProcessData.withItems(5))
300
301
// Test transition logic
302
fsmRef ! ProcessItem("final-item")
303
assert(fsmRef.stateName == Complete)
304
305
// Test timer functionality
306
assert(fsmRef.isStateTimerActive)
307
fsmRef.startTimerWithFixedDelay("heartbeat", "ping", 100.millis)
308
assert(fsmRef.isTimerActive("heartbeat"))
309
```
310
311
### State Inspection and Debugging
312
313
TestFSMRef provides detailed state inspection for debugging FSM behavior.
314
315
```scala
316
val fsmRef = TestFSMRef(new ComplexFSM)
317
318
// Inspect current state at any time
319
println(s"Current state: ${fsmRef.stateName}")
320
println(s"Current data: ${fsmRef.stateData}")
321
322
// Check timer status for debugging
323
println(s"State timer active: ${fsmRef.isStateTimerActive}")
324
println(s"Custom timer active: ${fsmRef.isTimerActive("custom")}")
325
326
// Set breakpoints in state for step-by-step testing
327
fsmRef.setState(CriticalState, criticalData)
328
// ... perform tests on critical state
329
```
330
331
### Type Safety
332
333
TestFSMRef maintains full type safety for FSM state names and data types.
334
335
```scala
336
// Type parameters ensure compile-time safety
337
val fsmRef: TestFSMRef[MyState, MyData, MyFSMActor] =
338
TestFSMRef(new MyFSMActor)
339
340
// State name is strongly typed
341
val currentState: MyState = fsmRef.stateName
342
343
// State data is strongly typed
344
val currentData: MyData = fsmRef.stateData
345
346
// setState requires correct types
347
fsmRef.setState(ValidState, validData) // ✓ Compiles
348
// fsmRef.setState("invalid", wrongData) // ✗ Compile error
349
```