42

I have some Java stuff like this:

public interface EventBus{
    void fireEvent(GwtEvent<?> event);
}


public class SaveCommentEvent extends GwtEvent<?>{
    private finalComment oldComment;
    private final Comment newComment;

    public SaveCommentEvent(Comment oldComment,Comment newComment){
        this.oldComment=oldComment;
        this.newComment=newComment;
    }

    public Comment getOldComment(){...}
    public Comment getNewComment(){...}
}

and test code like this:

  def "...."(){
     EventBus eventBus=Mock()
     Comment oldComment=Mock()
     Comment newCommnet=Mock()

     when:
         eventBus.fireEvent(new SaveCommentEvent(oldComment,newComment))

     then:
         1*eventBus.fireEvent(
                                {
                                   it.source.getClass()==SaveCommentEvent;
                                   it.oldComment==oldComment;
                                   it.newComment==newComment
                                 }
                              )            
}

I want to verify that the eventBus.fireEvent(..) gets called once with an Event with type SaveCommentEvent and construction parameters oldComment and newComment.

Code runs without errors but problem is:

After changing closure stuff from

{
   it.source.getClass()==SaveCommentEvent;
   it.oldComment==oldComment;  //old==old
   it.newComment==newComment   //new==new
}

To

 {
    it.source.getClass()==Other_Class_Literal;
    it.oldComment==newComment;  //old==new
    it.newComment==oldComment   //new==old
  }

Still, code runs without error? Apparently the closure didn't do what I want, so the question is: How to do argument capturing?

Graham Russell
  • 997
  • 13
  • 24
Alex Luya
  • 9,412
  • 15
  • 59
  • 91

6 Answers6

65

I got it:

    SaveCommentEvent firedEvent

    given:
     ...

    when:
     ....

    then:
    1 * eventBus.fireEvent(_) >> {arguments -> firedEvent=arguments[0]}
    firedEvent instanceof SaveModelEvent
    firedEvent.newModel == newModel
    firedEvent.oldModel == oldModel
Graham Russell
  • 997
  • 13
  • 24
Alex Luya
  • 9,412
  • 15
  • 59
  • 91
  • 1
    Or replace `;` with `&&` in the original code. (Code argument constraints need to return `true` of `false` depending on whether they matched or not.) – Peter Niederwieser Mar 04 '14 at 08:46
  • I have found it impossible to access the captured argument outside of the closure, regardless of where the variable is defined. – orbfish Jun 21 '16 at 18:34
  • The main benefit of using this method and not just replacing ; with && in the original code is that you get more fine tuned error messages - you know exactly what condition failed – Amit Goldstein Jun 28 '18 at 11:18
  • 2
    Spock seems to confuse the arguments captured as return value for the mock. How is it working for everyone else? @AlexLuya – Vishal Sep 26 '18 at 16:36
  • @Vishal The closure will return the last line as the return value for the mock, so if the return type is different from the argument type, then you should explicitly return an object of the correct type from the closure – Patrick Mar 23 '20 at 19:17
  • This answer is much nicer if you want properly typed arguments: https://stackoverflow.com/a/67916541/1852005 – stwr667 May 26 '23 at 09:04
20
then:
     1*eventBus.fireEvent(
                            {
                               it.source.getClass()==SaveCommentEvent;
                               it.oldComment==oldComment;
                               it.newComment==newComment
                             }
                          )            

In your code it is a Groovy Closure Implicit Variable reference to a mock eventBus Interface which has no fields. How could you verify them?

Also, I think the order of events that has to happen to use Spock Mocks is not necessarily intuitive. I would write it up here except it would not be as good as Kenneth Kousen's explanation.

jeremyjjbrown
  • 7,772
  • 5
  • 43
  • 55
  • 5
    @jeremyjjbrown If I am not mistaken, the code you have provided is not 100% correct. Specifically the test will only fail if the `it.newComment==newComment` check does not pass. The previous two checks will not have any effect on the passing or failing of the test. This what @PeterNiederwieser implies in his comment – geoand May 17 '17 at 10:54
  • 1
    This answer is wrong as @geoand said. The first two lines are executed, but their result is ignored. Either use it like this: then: 1*eventBus.fireEvent( { it.source.getClass()==SaveCommentEvent && it.oldComment==oldComment && it.newComment==newComment } ) or try some other approach. – nluk Jun 13 '22 at 07:45
11

In 2021 (7 yrs later) it is possible to do the following with groovy (2.5):

    ...

    then:
    1 * eventBus.fireEvent(_) >> { SaveModelEvent event ->
        assert event.newModel == newModel
        assert event.oldModel == oldModel
    }
    0 * _

.. which feels more handy to me and saves a line or two. :)

B_Osipiuk
  • 888
  • 7
  • 16
Stefan R.
  • 141
  • 1
  • 7
  • The only caveat it doesn't allow to declare behaviour of a mock in given block and do verification in then. Would not be a problem if mocking is not done in a separate methods for improving readability of the test – gaal Jun 16 '23 at 06:56
  • It also works for capturing multiple arguments, `1 * subject.dependencyClass.doSomething(_, _) >> { String a, String b -> a == b}` – prayagupa Aug 07 '23 at 16:51
10

Same idea with @Alex Luya but put the assertions in the closure and use assert on each of them. cf. Spock Framework Reference Documentation.

    then:
    1 * eventBus.fireEvent(_) >> {
        def firedEvent = it[0]
        assert firedEvent instanceof SaveModelEvent
        assert firedEvent.newModel == newModel
        assert firedEvent.oldModel == oldModel
    }
ryu1kn
  • 457
  • 5
  • 9
  • Thanks @mikerodent, my intention was to link to the multiple `assert`ions example; so the link looks correct in that regard. As for jeremyjjbrown's answer, I'm not sure... @geoand's pointing out that not all assertions are taking effects. I no longer have a snippet to experiment :p – ryu1kn Jan 13 '20 at 11:38
  • Ah, the thing under "Not very helpful. Fortunately, we can do better:"... I hadn't spotted that. Also yes, @geoand's point is correct. My mistake. – mike rodent Jan 13 '20 at 12:50
2

If you want to mock a method's response and also verify the same method's params(same as capturing the params), you can use Spock's code constraints (among other constraints) to partially match params, and at the same time, verify the method params. :

1 * list.add({
  verifyAll(it, Person) {
    firstname == 'William'
    lastname == 'Kirk'
    age == 45
  }
}) >> mockedResponse

PS: Solution inspired by this response from @Leonard Brünings

Power
  • 41
  • 5
1

In the answer from @alex-luya above, I found that the variable firedEvent needs the @Shared annotation.

Then I can capture the value and run my checks on the value outside the closure.

T.V.
  • 793
  • 12
  • 33
Werd
  • 47
  • 7