spock 中不可预测的存根初始化
Unpredicatble stub initialization in spock
这是一个示例 spock 规范方法(我知道测试返回的存根没有意义,这只是针对此问题的简化):
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)
}
我假设测试非常简单并且应该通过,但它给了我:
Expected exception of type 'java.lang.InterruptedException', but got 'java.lang.NullPointerException'
基本上,transferManager.upload() returns默认的null而不是配置的upload mock.
现在,如果我将 transferManager 初始化更改为:
var transferManager = Mock(TransferManager)
transferManager.upload(_,_,_) >> upload
它开始按预期工作。
在我看来,问题仅在存根使用另一个存根时才存在。比如直接使用upload stub时:
when:
upload.waitForCompletion()
它按预期工作(通过)。
此外,当我更改 transferManager 初始化以便它不使用另一个存根时:
var transferManager = Mock(TransferManager) {
throw new InterruptedException()
}
测试也通过了。
所以我的问题是,为什么这会按预期工作:
var transferManager = Mock(TransferManager)
transferManager.upload(_,_,_) >> upload
虽然没有正确设置上传方法:
var transferManager = Mock(TransferManager) {
upload(_,_,_) >> upload
}
?
很好,我想你发现了一个错误。
更新:我的回答完全错误,请无视。伦纳德说的完全正确(我又蠢又丑),我忽略了命名问题
我假设您使用 Spock 2.0 和 Java 10+ JDK,因为我在您的规范中看到 var
而不是 def
。但结果与 def
相同,即使在 Spock 1.3 和 Groovy 2.5.
上也是如此
我为您创建了一个 MCVE and Spock issue #1351。
这是因为您将变量命名为 upload
,它与您要在 TransferManager
上调用的方法同名。
var upload = Mock(Upload){
waitForCompletion() >> { throw new InterruptedException() }
}
var transferManager = Mock(TransferManager) {
upload(_,_,_) >> upload
}
在这种情况下,局部变量比闭包委托具有更高的优先级,这就是事情出错的原因。
我认为我们在 Spock 方面无能为力,因为这就是 groovy 的工作方式。
def upload = new Upload()
def transferManager = new TransferManager()
transferManager.with {
upload("a", "b", "c")
}
将失败 groovy.lang.MissingMethodException: No signature of method: Upload.call() is applicable for argument types: (String, String, String) values: [a, b, c]
修复该示例的最简单方法是将变量重命名为不冲突的名称。或者,您可以在存根前加上它。使其明确无误。
这是一个示例 spock 规范方法(我知道测试返回的存根没有意义,这只是针对此问题的简化):
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)
}
我假设测试非常简单并且应该通过,但它给了我:
Expected exception of type 'java.lang.InterruptedException', but got 'java.lang.NullPointerException'
基本上,transferManager.upload() returns默认的null而不是配置的upload mock.
现在,如果我将 transferManager 初始化更改为:
var transferManager = Mock(TransferManager)
transferManager.upload(_,_,_) >> upload
它开始按预期工作。 在我看来,问题仅在存根使用另一个存根时才存在。比如直接使用upload stub时:
when:
upload.waitForCompletion()
它按预期工作(通过)。
此外,当我更改 transferManager 初始化以便它不使用另一个存根时:
var transferManager = Mock(TransferManager) {
throw new InterruptedException()
}
测试也通过了。
所以我的问题是,为什么这会按预期工作:
var transferManager = Mock(TransferManager)
transferManager.upload(_,_,_) >> upload
虽然没有正确设置上传方法:
var transferManager = Mock(TransferManager) {
upload(_,_,_) >> upload
}
?
很好,我想你发现了一个错误。
更新:我的回答完全错误,请无视。伦纳德说的完全正确(我又蠢又丑),我忽略了命名问题
我假设您使用 Spock 2.0 和 Java 10+ JDK,因为我在您的规范中看到 var
而不是 def
。但结果与 def
相同,即使在 Spock 1.3 和 Groovy 2.5.
我为您创建了一个 MCVE and Spock issue #1351。
这是因为您将变量命名为 upload
,它与您要在 TransferManager
上调用的方法同名。
var upload = Mock(Upload){
waitForCompletion() >> { throw new InterruptedException() }
}
var transferManager = Mock(TransferManager) {
upload(_,_,_) >> upload
}
在这种情况下,局部变量比闭包委托具有更高的优先级,这就是事情出错的原因。 我认为我们在 Spock 方面无能为力,因为这就是 groovy 的工作方式。
def upload = new Upload()
def transferManager = new TransferManager()
transferManager.with {
upload("a", "b", "c")
}
将失败 groovy.lang.MissingMethodException: No signature of method: Upload.call() is applicable for argument types: (String, String, String) values: [a, b, c]
修复该示例的最简单方法是将变量重命名为不冲突的名称。或者,您可以在存根前加上它。使其明确无误。