Validating call arguments with a closure when stubbing return value
Asked Answered
E

1

6

I have a question about validating arguments in a mock call with a closure. Sometimes I do this:

customerRepository.save({ Customer customer ->
        assert ...
        assert ...
    }) >> { ... some return value ... }

etc. i.e. multiple (but not too many) asserts in the closure, and also want to stub the call to return something. What I found out is that the code above doesn't work, I need to return a truthy value from the closure, otherwise the object I want to return is not returned and the test will fail somewhere else.

I don't think this is documented, could anybody say what the rules here are exactly?

Edit: actually, I've just checked and I need to return a truthy value even if I don't stub the return value.

Endothecium answered 16/8, 2016 at 7:11 Comment(0)
D
17

So far i know two options for validating arguments. Either match the arguments in-place which does not require asserts:

then:
1 * customerRepository.save({ it.id == 1 && it.name == "joe" }) >> returnValue

However, this will give you "too few invocations" if the validation fails which I find misleading in some cases and usually harder to debug.

Alternatively, match all arguments and assert in the implementation:

then:
1 * customerRepository.save(_) >> { Customer customer ->
    assert customer.id == 1
    assert customer.name == "joe"

    return returnValue
}

This will give you very descriptive assertion errors.

Drum answered 18/1, 2017 at 10:32 Comment(5)
Have you found any cleaner solution of doing this rather than defining method return value in then?Odyl
According to the Documentation for Interaction Based Testing, section Combining Mocking and Stubbing, it is explained that "when mocking and stubbing the same method call, they have to happen in the same interaction".Drum
How would you do this if you have multiple interactions in a 'then' block that can be matched in any order? It seems Spock fails as soon as an assertion fails when it tries to match the invocation to one of the interactions and it's not the right one. My code does not guarantee order (because it processes an unordered set).Steinbach
In this case you could try to combine both approaches above: Use argument matching to match calls to exactly one interaction and then use different asserts for these interactions. For example: 1 * foo.bar({ it.value < 0 }) >> { assert x } and 1 * foo.bar({ it.value >= 0 }) >> { assert y } instead of 2 * foo.bar(_) >> { assert x and y }.Drum
FYI: I wrote a small Spock extension to address one of the possible issues with the approach outlined here: gitlab.com/mwhipple/spock-thenclose-extension (and also just added a TODO for myself at some point to allow plugging that into the mock definition which could avoid the need for the closure).Edd

© 2022 - 2024 — McMap. All rights reserved.