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.
Specialized TestActorRef for testing FSM (Finite State Machine) actors with state inspection and timer management.
/**
* Specialized TestActorRef for FSM actors
* Provides direct access to FSM state and timer management
* @param system ActorSystem to use
* @param props Props for creating the FSM actor
* @param supervisor Supervisor actor reference
* @param name Name for the test actor
*/
class TestFSMRef[S, D, T <: Actor](system: ActorSystem, props: Props,
supervisor: ActorRef, name: String)
extends TestActorRef[T] {
/**
* Get current FSM state name
* @return Current state name of type S
*/
def stateName: S
/**
* Get current FSM state data
* @return Current state data of type D
*/
def stateData: D
/**
* Manually set FSM state (for testing state transitions)
* @param stateName New state name
* @param stateData New state data
*/
def setState(stateName: S, stateData: D): Unit
/**
* Set FSM state with timeout and stop reason
* @param stateName New state name
* @param stateData New state data
* @param timeout State timeout duration
* @param stopReason Optional stop reason
*/
def setState(stateName: S, stateData: D, timeout: FiniteDuration,
stopReason: Option[FSM.Reason]): Unit
}Methods for managing FSM timers including starting, canceling, and checking timer status.
/**
* Start timer with fixed delay between executions
* @param name Timer name identifier
* @param msg Message to send when timer fires
* @param delay Delay between timer executions
*/
def startTimerWithFixedDelay(name: String, msg: Any, delay: FiniteDuration): Unit
/**
* Start timer with fixed rate execution
* @param name Timer name identifier
* @param msg Message to send when timer fires
* @param interval Fixed interval between timer executions
*/
def startTimerAtFixedRate(name: String, msg: Any, interval: FiniteDuration): Unit
/**
* Start single-shot timer
* @param name Timer name identifier
* @param msg Message to send when timer fires
* @param delay Delay before timer fires once
*/
def startSingleTimer(name: String, msg: Any, delay: FiniteDuration): Unit
/**
* Cancel named timer
* @param name Timer name to cancel
*/
def cancelTimer(name: String): Unit
/**
* Check if named timer is active
* @param name Timer name to check
* @return True if timer is active, false otherwise
*/
def isTimerActive(name: String): Boolean
/**
* Check if state timeout timer is active
* @return True if state timeout timer is active
*/
def isStateTimerActive: BooleanFactory methods for creating TestFSMRef instances with various parameter combinations.
object TestFSMRef {
/**
* Create TestFSMRef from actor factory function
* @param factory Function that creates FSM actor instance
* @param ev Evidence that T is an FSM[S, D]
* @param system Implicit ActorSystem
* @return TestFSMRef for the created FSM actor
*/
def apply[S, D, T <: Actor: ClassTag](factory: => T)
(implicit ev: T <:< FSM[S, D],
system: ActorSystem): TestFSMRef[S, D, T]
/**
* Create named TestFSMRef from actor factory function
* @param factory Function that creates FSM actor instance
* @param name Name for the actor
* @param ev Evidence that T is an FSM[S, D]
* @param system Implicit ActorSystem
* @return TestFSMRef for the created FSM actor
*/
def apply[S, D, T <: Actor: ClassTag](factory: => T, name: String)
(implicit ev: T <:< FSM[S, D],
system: ActorSystem): TestFSMRef[S, D, T]
/**
* Create TestFSMRef from Props
* @param props Props for creating the FSM actor
* @param ev Evidence that T is an FSM[S, D]
* @param system Implicit ActorSystem
* @return TestFSMRef for the created FSM actor
*/
def apply[S, D, T <: Actor](props: Props)
(implicit ev: T <:< FSM[S, D],
system: ActorSystem): TestFSMRef[S, D, T]
/**
* Create named TestFSMRef from Props
* @param props Props for creating the FSM actor
* @param name Name for the actor
* @param supervisor Optional supervisor reference
* @param ev Evidence that T is an FSM[S, D]
* @param system Implicit ActorSystem
* @return TestFSMRef for the created FSM actor
*/
def apply[S, D, T <: Actor](props: Props, name: String,
supervisor: Option[ActorRef] = None)
(implicit ev: T <:< FSM[S, D],
system: ActorSystem): TestFSMRef[S, D, T]
}Usage Examples:
import akka.actor.{ActorSystem, FSM, Props}
import akka.testkit.{TestFSMRef, TestKit}
import scala.concurrent.duration._
// Example FSM states and data
sealed trait State
case object Idle extends State
case object Active extends State
case object Completed extends State
case class Data(count: Int, items: List[String])
// Example FSM Actor
class ProcessorFSM extends FSM[State, Data] {
startWith(Idle, Data(0, Nil))
when(Idle) {
case Event("start", data) =>
goto(Active) using data.copy(count = 1)
}
when(Active, stateTimeout = 5.seconds) {
case Event("process", data) =>
val newData = data.copy(count = data.count + 1)
if (newData.count >= 10) {
goto(Completed) using newData
} else {
stay() using newData
}
case Event(StateTimeout, data) =>
goto(Idle) using data.copy(count = 0)
}
when(Completed) {
case Event("reset", _) =>
goto(Idle) using Data(0, Nil)
}
}
class FSMTestExample extends TestKit(ActorSystem("test")) {
"ProcessorFSM" should {
"start in Idle state" in {
val fsmRef = TestFSMRef(new ProcessorFSM)
// Check initial state
assert(fsmRef.stateName == Idle)
assert(fsmRef.stateData == Data(0, Nil))
}
"transition from Idle to Active" in {
val fsmRef = TestFSMRef(new ProcessorFSM)
// Send transition message
fsmRef ! "start"
// Verify state change
assert(fsmRef.stateName == Active)
assert(fsmRef.stateData.count == 1)
}
"handle state timeout" in {
val fsmRef = TestFSMRef(new ProcessorFSM)
// Manually set state to Active
fsmRef.setState(Active, Data(5, Nil))
// Check state timeout timer
assert(fsmRef.isStateTimerActive)
// Trigger state timeout
fsmRef ! FSM.StateTimeout
// Verify transition back to Idle
assert(fsmRef.stateName == Idle)
assert(fsmRef.stateData.count == 0)
}
"manage custom timers" in {
val fsmRef = TestFSMRef(new ProcessorFSM)
// Start a custom timer
fsmRef.startSingleTimer("reminder", "remind", 1.second)
// Check timer status
assert(fsmRef.isTimerActive("reminder"))
// Cancel timer
fsmRef.cancelTimer("reminder")
assert(!fsmRef.isTimerActive("reminder"))
}
"support manual state manipulation" in {
val fsmRef = TestFSMRef(new ProcessorFSM)
// Manually set complex state for testing
val testData = Data(8, List("item1", "item2"))
fsmRef.setState(Active, testData)
// Verify manual state setting
assert(fsmRef.stateName == Active)
assert(fsmRef.stateData == testData)
// Continue with normal FSM behavior
fsmRef ! "process"
fsmRef ! "process"
// Should transition to Completed (count reaches 10)
assert(fsmRef.stateName == Completed)
assert(fsmRef.stateData.count == 10)
}
}
}TestFSMRef integrates with Akka FSM features for comprehensive testing.
// Testing FSM with complex data transformations
class DataProcessorFSM extends FSM[ProcessState, ProcessData] {
when(Processing) {
case Event(ProcessItem(item), data) =>
val newData = data.addItem(item)
if (newData.isComplete) {
goto(Complete) using newData forMax 30.seconds
} else {
stay() using newData
}
}
}
// Test complex FSM scenarios
val fsmRef = TestFSMRef(new DataProcessorFSM)
// Set up specific test scenario
fsmRef.setState(Processing, ProcessData.withItems(5))
// Test transition logic
fsmRef ! ProcessItem("final-item")
assert(fsmRef.stateName == Complete)
// Test timer functionality
assert(fsmRef.isStateTimerActive)
fsmRef.startTimerWithFixedDelay("heartbeat", "ping", 100.millis)
assert(fsmRef.isTimerActive("heartbeat"))TestFSMRef provides detailed state inspection for debugging FSM behavior.
val fsmRef = TestFSMRef(new ComplexFSM)
// Inspect current state at any time
println(s"Current state: ${fsmRef.stateName}")
println(s"Current data: ${fsmRef.stateData}")
// Check timer status for debugging
println(s"State timer active: ${fsmRef.isStateTimerActive}")
println(s"Custom timer active: ${fsmRef.isTimerActive("custom")}")
// Set breakpoints in state for step-by-step testing
fsmRef.setState(CriticalState, criticalData)
// ... perform tests on critical stateTestFSMRef maintains full type safety for FSM state names and data types.
// Type parameters ensure compile-time safety
val fsmRef: TestFSMRef[MyState, MyData, MyFSMActor] =
TestFSMRef(new MyFSMActor)
// State name is strongly typed
val currentState: MyState = fsmRef.stateName
// State data is strongly typed
val currentData: MyData = fsmRef.stateData
// setState requires correct types
fsmRef.setState(ValidState, validData) // ✓ Compiles
// fsmRef.setState("invalid", wrongData) // ✗ Compile error