Search code examples
groovyfieldspockgetter-setter

How can I test that a field is set to a certain value?


This seems basic so I'm expecting this to be a dupe... but I haven't found anything that answers this question.

My app code is also Groovy. Say I have a field

def something

and in my test (where the CUT is a Spock Spy) I run a method in the middle of which there is a line

something = null 

or

something = new Bubble()

... I'm simply trying to find a way of testing that something has indeed been set to null (or any value...)

In my then block I've tried:

1 * spyCUT.setSomething( null ) 

and

1 * spyCUT.setSomething(_)

and

1 * spyCUT.set( 'something', _ )

Incidentally, in answer to the objection that I could just test the value of something in the then block, the situation is that something is meant to be set first to one value and then to another in the course of this method...

Having read Groovy In Action 2nd Ed I have the vaguest of notions about how Groovy goes about dealing with getting and setting fields... Not enough, clearly.

MCVE (FWIW!)

class Spocko {
    def something

    def doStuff() {
        something = 'fruit'
    }
}

class SpockoTest extends Specification {
    def 'test it'(){
        given:
        Spocko spySpocko = Spy( Spocko )

        when:
        spySpocko.doStuff()

        then:
        1 * spySpocko.setSomething(_)
    }
}

LATER (after kriegaex's very helpful reply)

With above SpockTest where setSomething is invoked:

class Spocko {
    def something

    def doStuff() {
        this.each{
            it.something = 'fruit' 
        }
    }
}

... passes! I'm trying now to understand why...

Incidentally I also find that the following passes (and doesn't without the closure):

1 * spySpocko.setProperty( 'something', _ )

Solution

  • After I have seen your MCVE, the question can be answered as follows: You cannot test for a method call which never happens. doStuff() just assigns a value to a field, it does not call a setter method internally. Look at this:

    package de.scrum_master.stackoverflow
    
    import spock.lang.Specification
    
    class SpockoTest extends Specification {
      static class Spocko {
        def something
    
        def doStuff() {
          something = 'fruit'
        }
    
        def doMoreStuff() {
          setSomething('vegetable')
        }
      }
    
      def 'test it'(){
        given: 'Spocko spy'
        Spocko spySpocko = Spy(Spocko)
    
        when: 'calling method assigning value to property'
        spySpocko.doStuff()
    
        then: 'no setter is called'
        0 * spySpocko.setSomething(_)
        spySpocko.something == 'fruit'
    
        when: 'calling method using setter'
        spySpocko.doMoreStuff()
    
        then: 'setter gets called'
        1 * spySpocko.setSomething('vegetable')
    
        when: 'using Groovy setter-like syntax from another class'
        spySpocko.something = 'fish'
    
        then: 'actually a setter gets called'
        1 * spySpocko.setSomething('fish')
      }
    }
    

    This is what happens. When calling

    javap -v target/test-classes/de/scrum_master/stackoverflow/SpockoTest\$Spocko.class
    

    you see (output shortened):

    public java.lang.Object doStuff();
      descriptor: ()Ljava/lang/Object;
      flags: ACC_PUBLIC
      Code:
        stack=2, locals=3, args_size=1
           0: invokestatic  #24                 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
           3: astore_1
           4: ldc           #36                 // String fruit
           6: astore_2
           7: aload_2
           8: aload_0
           9: swap
          10: putfield      #38                 // Field something:Ljava/lang/Object;
          13: aload_2
          14: areturn
          15: aconst_null
          16: areturn
    
    public java.lang.Object doMoreStuff();
      descriptor: ()Ljava/lang/Object;
      flags: ACC_PUBLIC
      Code:
        stack=3, locals=2, args_size=1
           0: invokestatic  #24                 // Method $getCallSiteArray:()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
           3: astore_1
           4: aload_1
           5: ldc           #40                 // int 0
           7: aaload
           8: aload_0
           9: ldc           #42                 // String vegetable
          11: invokeinterface #48,  3           // InterfaceMethod org/codehaus/groovy/runtime/callsite/CallSite.callCurrent:(Lgroovy/lang/GroovyObject;Ljava/lang/Object;)Ljava/lang/Object;
          16: areturn
          17: aconst_null
          18: areturn
    

    Can you spot the difference?


    Update after question edit 2: You wanted to know why this triggers the setter call:

    def doStuff() {
      this.each {
        it.something = 'fruit' 
      }
    }
    

    This is because this is provided to the closure as a parameter, thus it.something = 'fruit' gets resolved dynamically just like in my example spySpocko.something = 'fish' because it is not an internal assignment like in something = 'fruit' (equivalent to this.something = 'fruit') anymore.

    Actually I think this is not so difficult to understand even without looking at bytecode, just following the usual Groovy tutorials. I am repeating myself, but I do think you are over-engineering and over-complicating things a bit, testing things too deeply. I would not put tests like these into a production code base. Try to test the behaviour of your classes (think specifications and features!), not the innards' intricacies. But if it helps you understand how Groovy works, just continue playing.

    As of now, please refrain from further question edits and follow-up questions. If you have a new problem, it would be better to create a new question with a new MCVE.