testinggroovyparametersspockunroll

Spock Unroll seems to print something odd with boolean parameter


I just put this test method together:

@Unroll
def 'super start edit should be called if cell is not empty'( boolean empty ){
    given:
    DueDateEditor editor = GroovySpy( DueDateEditor ){
        isEmpty() >> empty
    }

    when:
    editor.startEdit()

    then:
    if( empty){
        0 * editor.callSuperStartEdit()
    }
    else {
        1 * editor.callSuperStartEdit()
    }

    where:
    empty | _
    true  | _
    false | _
}

... it works OK in terms of the two tests passing... but when you make it fail it's very odd: the output if the parameter empty is false is

super start edit should be called if cell is not empty[1]

... and it is 0 if the parameter empty is true. Is this a bug?


Solution

  • I am writing an additional answer because

    package de.scrum_master.stackoverflow.q61032514;
    
    import java.time.LocalDate;
    
    public class DueDateEditor {
      String text;
    
      public boolean isEmpty() {
        return text == null || text.trim() == "";
      }
    
      public void startEdit() {
        if (!isEmpty())
          callSuperStartEdit();
      }
    
      public void callSuperStartEdit() {}
    }
    
    package de.scrum_master.stackoverflow.q61032514
    
    import spock.lang.Specification
    import spock.lang.Unroll
    
    class DueDateEditorTest extends Specification {
      @Unroll
      def 'super start edit #shouldMsg be called if the cell is #cellStateMsg'() {
        given:
        DueDateEditor editor = Spy() {
          isEmpty() >> empty
        }
    
        when:
        editor.startEdit()
    
        then:
        (empty ? 0 : 1) * editor.callSuperStartEdit()
    
        where:
        empty << [true, false]
        shouldMsg = empty ? 'should not' : 'should'
        cellStateMsg = empty ? 'empty' : 'not empty'
      }
    
      @Unroll
      def "super start edit #shouldMsg be called if cell text is '#text'"() {
        given:
        DueDateEditor editor = Spy()
        editor.text = text
    
        when:
        editor.startEdit()
    
        then:
        (editor.isEmpty() ? 0 : 1) * editor.callSuperStartEdit()
        // Or, if 'isEmpty()' has a side effect:
        // (text ? 1 : 0) * editor.callSuperStartEdit()
    
        where:
        text << ["foo", "", null, "line 1\nline 2"]
        shouldMsg = text ? 'should' : 'should not'
        cellStateMsg = text ? 'not empty' : 'empty'
      }
    }
    

    General remarks:

    P.S.: Sorry, I had to make up a sample class under test DueDateEditor in order to make my test compile and run as expected. As usual, Mike unfortunately didn't provide an MCVE but just a part of it.


    Update: We talked about GroovySpy in our comments and, as I said, it will not work if your classes are Java classes and there is a final method in you want to stub, see the Spock manual. Here is proof for you:

    package de.scrum_master.stackoverflow.q61032514;
    
    public class TreeTableCell<A, B> {
      String text;
    
      public final boolean isEmpty() {
        return text == null || text.trim() == "";
      }
    }
    
    package de.scrum_master.stackoverflow.q61032514;
    
    import java.time.LocalDate;
    
    public class DueDateEditor extends TreeTableCell<String, LocalDate> {
      public void startEdit() {
        if (!isEmpty())
          callSuperStartEdit();
      }
    
      public void callSuperStartEdit() {}
    }
    
    package de.scrum_master.stackoverflow.q61032514
    
    import spock.lang.Specification
    import spock.lang.Unroll
    
    class DueDateEditorTest extends Specification {
      @Unroll
      def 'super start edit #shouldMsg be called if the cell is #cellStateMsg'() {
        given:
        DueDateEditor editor = GroovySpy() {
          isEmpty() >> empty
        }
    
        when:
        editor.startEdit()
    
        then:
        (empty ? 0 : 1) * editor.callSuperStartEdit()
    
        where:
        empty << [true, false]
        shouldMsg = empty ? 'should not' : 'should'
        cellStateMsg = empty ? 'empty' : 'not empty'
      }
    }
    

    The test would work if your application classes were Groovy classes only. But if they are Java classes like in my example, the test will fail like this:

    Too few invocations for:
    
    (empty ? 0 : 1) * editor.callSuperStartEdit()   (0 invocations)
    
    Unmatched invocations (ordered by similarity):
    
    1 * editor.startEdit()
    methodName == "callSuperStartEdit"
    |          |
    startEdit  false
               10 differences (44% similarity)
               (s---------)tartEdit
               (callSuperS)tartEdit
    

    So in this case you cannot just use Groovy magic to check interactions. But as I said, you shouldn't do that anyway. Rather make sure that both startEdit() and callSuperStartEdit() do the right things. Check their results or, if they are void, check their side effects on the state of the subject under test or its collaborators.


    Update 2: Regarding your original question about indexed method naming, actually @tim_yates gave the correct answer. I just want to add the corresponding Spock manual link explaining method unrolling and how you can influence naming using variables from the where: block.