Testing

Mechanoid machines are ordinary ZIO values. Prefer the same scoped start / send style in unit tests that you use on this site.

Scoped unit tests

Assert outcomes and state after a short path:

{
  enum Light derives Finite:
    case Red, Green

  enum Tick derives Finite:
    case Go, Timeout

  import Light.*, Tick.*

  val machine = Machine(
    assembly[Light, Tick](
      (Red via Go to Green) @@ Aspect.timeout(1.minute, Timeout),
      Green via Timeout to Red,
    )
  )

  ZIO.scoped {
    for
      fsm     <- machine.start(Red)
      outcome <- fsm.send(Go)
      state   <- fsm.currentState
    yield (outcome.result.toString, state.toString)
  }.asDoc
}
(Goto(Green),Green)

Negative paths

Illegal events surface as InvalidTransitionError (use .either):

{
  enum Light derives Finite:
    case Red, Green

  enum Tick derives Finite:
    case Go, Timeout

  import Light.*, Tick.*

  val machine = Machine(
    assembly[Light, Tick](
      (Red via Go to Green) @@ Aspect.timeout(1.minute, Timeout),
      Green via Timeout to Red,
    )
  )

  ZIO.scoped {
    for
      fsm    <- machine.start(Red)
      failed <- fsm.send(Timeout).either
    yield failed
  }.asDoc
}
Left(InvalidTransitionError(Red,Timeout,No transition defined))

Timeouts in tests

ApproachWhen
Send the timeout eventDeterministic DocSpecs / live clock (this site)
TestClock.adjustFiber timeouts in zio-test without sleeping wall time

This site uses TestAspect.withLiveClock because EventStore timestamps and producing sleeps need a real clock; synthetic timeout events keep examples snappy.

{
  enum Light derives Finite:
    case Red, Green

  enum Tick derives Finite:
    case Go, Timeout

  import Light.*, Tick.*

  val machine = Machine(
    assembly[Light, Tick](
      (Red via Go to Green) @@ Aspect.timeout(1.minute, Timeout),
      Green via Timeout to Red,
    )
  )

  ZIO.scoped {
    for
      fsm   <- machine.start(Red)
      _     <- fsm.send(Go)
      _     <- fsm.send(Timeout)
      state <- fsm.currentState
    yield state
  }.asDoc
}
Red

Layers and docs-as-tests

Reuse the same InMemoryEventStore / TimeoutStrategy / LockingStrategy layers as the production ladder pages. Specular DocSpecs are the suite: docs/test and docs/specularSite fail when an assertion fails. Machines rendered with mermoid parse the Mermaid Mechanoid emits, so the published picture cannot drift from the graph you run.

See Visualization and the Domains pages.