Reputation: 747
This is a sample spock spec method (I know it doesn't make sense to test what stub is returning, this is a simplification for this question purpose only):
def "my test"() {
given:
var upload = Mock(Upload){
waitForCompletion() >> { throw new InterruptedException() }
}
var transferManager = Mock(TransferManager) {
upload(_,_,_) >> upload
}
when:
var up = transferManager.upload(null, null, null)
up.waitForCompletion()
then:
thrown(InterruptedException)
}
I assume that the test is quite straightforward and it should pass, but it gives me:
Expected exception of type 'java.lang.InterruptedException', but got 'java.lang.NullPointerException'
Basically, transferManager.upload() returns the default null instead of configured upload mock.
Now, if I change transferManager initialization to this:
var transferManager = Mock(TransferManager)
transferManager.upload(_,_,_) >> upload
It starts to work as expected. It seems to me that the problem exists only when stub uses another stub. For example, when using upload stub directly:
when:
upload.waitForCompletion()
it works as expected (it passes).
Also when I change transferManager initialization so that it doesn't use another stub:
var transferManager = Mock(TransferManager) {
throw new InterruptedException()
}
the test also passes.
So my question is, why this works as expected:
var transferManager = Mock(TransferManager)
transferManager.upload(_,_,_) >> upload
while that doesn't set up upload method properly:
var transferManager = Mock(TransferManager) {
upload(_,_,_) >> upload
}
?
Upvotes: 1
Views: 347
Reputation: 67417
Good catch, I think you found a bug.
Update: My answer is plain wrong, please disregard it. Leonard is absolutely right (and I am stupid and ugly), I overlooked the naming issue.
I am assuming you use Spock 2.0 and a Java 10+ JDK, because I see var
instead of def
in your specification. But the result is the same with def
, even on Spock 1.3 and Groovy 2.5.
I created an MCVE and Spock issue #1351 on your behalf.
Upvotes: 2
Reputation: 13242
This is caused because you named the variable upload
which is the same name as the method you want to call on TransferManager
.
var upload = Mock(Upload){
waitForCompletion() >> { throw new InterruptedException() }
}
var transferManager = Mock(TransferManager) {
upload(_,_,_) >> upload
}
In this case the local variable has a higher precedence over the delegation of the closure, which is why things go awry. I don't think there is much we can do on the Spock side as this how groovy works.
def upload = new Upload()
def transferManager = new TransferManager()
transferManager.with {
upload("a", "b", "c")
}
will fail with groovy.lang.MissingMethodException: No signature of method: Upload.call() is applicable for argument types: (String, String, String) values: [a, b, c]
The easiest way to fix the example, is to rename the variable to something non-conflicting. Alternatively, you can prefix the stubbing with it. to make it unambiguous.
Upvotes: 4