Search code examples
javagroovyjunitspock

Test a state machine using spock framework


I have built a state machine in Java and I am trying to unit test the same with spock framework with all the possible combinations of the state that the input can make a transition. I prefer to capture the possible combinations in a data table but I am facing a challenge in iterating a list of values as input in the data table. Please refer to the below test :

@Unroll("#current state can successfully transit to #next")
def "Validate the successful transition of the state machine"() {
    given:
    when:
    def result = current.canTransitionTo(next)
    then:
    result == true

    where:
    current | next
    STATE_A | STATE_B // Works 
    STATE_A | STATE_C // Works
    STATE_A | [STATE_B, STATE_C] // This approach won't work since canTransitionTo() is not expecting an arraylist
}

The first two approaches work, however I am searching for a way to make the third approach working where the spock framework need to iterate through all the possible next values since canTransitionTo() is designed to take a single state and not arraylist and validate the result.

Has anyone done something similar. I'm bit new to spock, but wanted to learn if the third approach could be made possible to test, else please suggest a better way to test this state machine


Solution

  • Like you said:

    This approach won't work since canTransitionTo() is not expecting an arraylist

    Therefore, you need to make sure to iterate over the list. Thanks to Groovy goodness, you have convenience methods for that. How about next.every { current.canTransitionTo(it) }?

    package de.scrum_master.stackoverflow.q68674245
    
    import spock.lang.Specification
    import spock.lang.Unroll
    
    import static de.scrum_master.stackoverflow.q68674245.StateMachineTest.StateMachine.*
    
    class StateMachineTest extends Specification {
      @Unroll("#current state can successfully transit to #next")
      def "Validate the successful transition of the state machine"() {
        expect:
        next.every { current.canTransitionTo(it) }
    
        where:
        current | next
        STATE_A | STATE_B
        STATE_A | STATE_C
        STATE_A | [STATE_B, STATE_C]
        START   | [STATE_A, STATE_B, STATE_C]
        STATE_C | [STATE_B, START]  // fails, because START is a forbidden target state
      }
    
      static enum StateMachine {
        START, STATE_A, STATE_B, STATE_C
    
        boolean canTransitionTo(StateMachine stateMachine) {
          println "transition $this -> $stateMachine"
          return stateMachine != START
        }
      }
    }
    

    You can even go further and allow iterables on both ends of the transition:

    package de.scrum_master.stackoverflow.q68674245
    
    import spock.lang.Specification
    import spock.lang.Unroll
    
    import static de.scrum_master.stackoverflow.q68674245.StateMachineTest.StateMachine.*
    
    class StateMachineTest extends Specification {
      @Unroll("#current can transit to #next")
      def "Validate successful transition of the state machine"() {
        expect:
        current.every { from -> next.every { to -> from.canTransitionTo(to) } }
    
        where:
        current                            | next
        [START, STATE_A, STATE_B, STATE_C] | [STATE_A, STATE_B, STATE_C]
      }
    
      @Unroll("#current must not transit to #next")
      def "Validate failed state machine transition"() {
        expect:
        current.every { from -> next.every { to -> !from.canTransitionTo(to) } }
    
        where:
        current                            | next
        [START, STATE_A, STATE_B, STATE_C] | START
      }
    
      static enum StateMachine {
        START, STATE_A, STATE_B, STATE_C
    
        boolean canTransitionTo(StateMachine stateMachine) {
          println "transition $this -> $stateMachine"
          return stateMachine != START
        }
      }
    }
    

    P.S.: In the above case, you actually do not need where blocks, if the data tables only consist of single lines. But I am assuming that your state machine is a bit more complex than my example and you need to specify more differentiated cases.