or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-testing.mddeterministic-execution.mdevent-filtering.mdfsm-testing.mdindex.mdsynchronization.mdsynchronous-testing.mdtest-utilities.md
tile.json

fsm-testing.mddocs/

FSM Testing

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.

Capabilities

TestFSMRef Class

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
}

Timer Management

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: Boolean

Factory Methods

Factory 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)
    }
  }
}

FSM Integration Features

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"))

State Inspection and Debugging

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 state

Type Safety

TestFSMRef 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