Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test valid state transitions with scalacheck

Suppose I have this class:

case class Receipt(id: Long, state: String) {
  def transitionTo(newState: String) = {
    if (!canTransitionTo(newState)) {
       throw new IllegalStateExcetion(s"cant transition from $state to $newState")
    }
    this.copy(state = newState)
  }
}

I'd like to test the logic in canTransitionTo (not included here for the sake of simplicity) with scalachecks Commands but I'm having a bit of trouble on how to begin. Any ideas?

like image 966
Pablo Fernandez Avatar asked Nov 08 '22 14:11

Pablo Fernandez


1 Answers

There are some tutorials how to test state machines with this framework but they test another property. Usually they create a Command for each valid transition and fire scalacheck to do any random combinations of them. The goal of such property is to verify that state machine behaves normally for any number of valid transitions.

This approach will not test canTransitionTo because it assumes all transitions are valid. Testing transitions between any pair of states will require to reimplement a notion of valid and invalid transitions in terms of scalacheck. This could be even more complex then original canTransitionTo function.


If one of transition sets is much smaller than other scalacheck can help to generate another one. For example if there are only handful of valid transitions and tenth of invalid then generators can help.

private val allStates: Gen[String] = Gen.oneOf("State1", "State2", "State3")

private val validTransitions: Set[(String, String)] = Set("State1" -> "State2", "State2" -> "State3", "State3" -> "State1")
private val validTransitionsGen: Gen[(String, String)] = Gen.oneOf(validTransitions.toSeq)

private val invalidTransition: Gen[(String, String)] = for {
  from <- allStates
  to <- allStates
  if !validTransitions.contains(from -> to) //this is reimplementaion of canTransitionTo
} yield from -> to

property("valid transitions") = forAll(validTransitionsGen) { transition =>
  Receipt(0, transition._1).canTransitionTo(transition._2)
}

property("invalid transitions") = forAll(invalidTransition) { transition =>
  !Receipt(0, transition._1).canTransitionTo(transition._2)
}
like image 162
Nazarii Bardiuk Avatar answered Nov 14 '22 23:11

Nazarii Bardiuk