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', _ )
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.