I am also struggling to find a method I like. I’m not a big fan of creating a new message just for testing. How do you make sure it’s not used in production?
For simple cases, I’ve found injecting a function the actor uses to change state can allow me to verify a behavior produces the desired next behavior AND that it produces it with the desired next state.
I’m using ScalaMock. https://scalamock.org/
import akka.actor.testkit.typed.scaladsl.BehaviorTestKit
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import org.scalamock.scalatest.MockFactory
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.must.Matchers
sealed trait Command
final case class Increase(value: Int) extends Command
final case class Decrease(value: Int) extends Command
object Counter {
type NextBehavior = (State => Behavior[Command], State) => Behavior[Command]
case class State(count: Int,
next: NextBehavior = (next, state) => next(state))
val singleBehavior: State => Behavior[Command] = { state =>
Behaviors.receiveMessage {
case Increase(increment) =>
state.next(singleBehavior, state.copy(count = state.count + increment))
case Decrease(decrement) =>
state.next(singleBehavior, state.copy(count = state.count - decrement))
}
}
def apply(next: NextBehavior = (next, state) => next(state)): Behavior[Command] = {
next(singleBehavior, State(0, next))
}
}
class CounterSpec extends AnyFlatSpec with Matchers with MockFactory {
import Counter._
sealed trait NextFixture {
val mockNext = mockFunction[State => Behavior[Command], State, Behavior[Command]]
}
"Counter.apply" must "initialize to zero with the provided next function" in new NextFixture {
mockNext.expects(singleBehavior, State(0, mockNext)).returning(Behaviors.empty)
BehaviorTestKit(Counter(mockNext)).returnedBehavior mustBe Behaviors.empty
}
"singleBehavior" must "increase the count by value when Increase(value)" in new NextFixture {
mockNext.expects(singleBehavior, State(5, mockNext)).returning(Behaviors.empty)
val sut = BehaviorTestKit(singleBehavior(State(0, mockNext)))
sut.run(Increase(5))
sut.returnedBehavior mustBe Behaviors.empty
}
it must "decrease the count by value when Decrease(value)" in new NextFixture {
mockNext.expects(singleBehavior, State(5, mockNext)).returning(Behaviors.empty)
val sut = BehaviorTestKit(singleBehavior(State(10, mockNext)))
sut.run(Decrease(5))
sut.returnedBehavior mustBe Behaviors.empty
}
}