斯波克虫?存根上的 callRealMethod() 不会抛出预期的异常
Spock bug? callRealMethod() on stub does not throw expected exception
摘自 class CUT
待测:
def compileOutputLines( TopDocs topDocs ) {
println "gubbins"
}
测试代码:
def "my feature"(){
given:
CUT stubCut = Stub( CUT ){
compileOutputLines(_) >> { TopDocs mockTD ->
// NB no Exception is thrown
// try {
println "babbles"
callRealMethod()
println "bubbles"
// }catch( Exception e ) {
// println "exception $e"
// }
}
}
CUT spyCut = Spy( CUT ){
compileOutputLines(_) >> { TopDocs mockTD ->
println "babbles 2"
callRealMethod()
println "bubbles 2"
}
}
when:
stubCut.compileOutputLines( Mock( TopDocs ))
spyCut.compileOutputLines( Mock( TopDocs ))
then:
true
}
输出到stdout
:
babbles
bubbles
babbles 2
gubbins
bubbles 2
我试图在网上找到 link 完整的 Spock Framework Javadoc...但我找不到..."non-framework" Javadoc 是 here,但是您不会在索引中找到方法 callRealMethod
。
从我从源码本地生成的JavadocAPI中,确实可以找到这个方法:是org.spockframework.mock.IMockInvocation
的一个方法。它说:
java.lang.Object callRealMethod()
Delegates this method invocation to the real object underlying this
mock object, including any method arguments. If this mock object has
no underlying real object, a CannotInvokeRealMethodException is
thrown.
Returns:
the return value of the method to which this invocation was delegated
我的理解(事实上)是 Stub 应该导致此 Exception
被抛出。但它似乎不是。路过的专家有什么意见吗?
前言
这是一个有趣的问题。理论上我的答案是:
callRealMethod()
is only available for spies, not for mocks or stubs. It is also only mentioned in the chapter about spies, did you notice?
Think about it: A spy wraps a real object, so there you have a reference to a real method you can call. The same is not true for mocks and stubs which are just no-op subclasses. If you could call a real method for a stub, it would be a spy.
谜题
实际上,在我自己使用 Spock 1.1 (Groovy 2.4) 进行的测试中,我看到了与您不同甚至更奇怪的行为:无论我使用 mock、stub 还是 spy,callRealMethod()
总是调用真正的方法。这真是一个惊喜。所以,是的,这种行为与我的预期不同。在调试时查看接口实现的源代码,我也看不到任何对模拟对象类型的检查(是否是间谍?)。真正的方法只是识别和调用。
解决方案
查看 class DynamicProxyMockInterceptorAdapter
我找到了对此行为的解释:IMockInvocation
Javadoc 中提到的异常仅在尝试调用接口类型的真实方法时抛出模拟,从不用于模拟或 class 类型的对象:
public Object invoke(Object target, Method method, Object[] arguments) throws Throwable {
IResponseGenerator realMethodInvoker = (ReflectionUtil.isDefault(method) || ReflectionUtil.isObjectMethod(method))
? new DefaultMethodInvoker(target, method, arguments)
: new FailingRealMethodInvoker("Cannot invoke real method '" + method.getName() + "' on interface based mock object");
return interceptor.intercept(target, method, arguments, realMethodInvoker);
}
所以句子“如果这个模拟对象没有底层的真实对象,抛出一个(...)异常”在本质上是正确的,但有歧义,因为它没有解释“底层真实对象”的含义。你的假设是错误的,我的也是。我们俩都吸取了教训。
现在您什么时候会看到所描述的行为?
package de.scrum_master.Whosebug;
public interface MyInterface {
void doSomething();
}
package de.scrum_master.Whosebug
import org.spockframework.mock.CannotInvokeRealMethodException
import spock.lang.Specification
class MyInterfaceTest extends Specification {
def "Try to call real method on interface mock"() {
given:
MyInterface myInterface = Mock() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
def "Try to call real method on interface stub"() {
given:
MyInterface myInterface = Stub() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
def "Try to call real method on interface spy"() {
given:
MyInterface myInterface = Spy() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
}
更新: 我刚刚创建了 issue #830 请求改进 Spock 的文档。
摘自 class CUT
待测:
def compileOutputLines( TopDocs topDocs ) {
println "gubbins"
}
测试代码:
def "my feature"(){
given:
CUT stubCut = Stub( CUT ){
compileOutputLines(_) >> { TopDocs mockTD ->
// NB no Exception is thrown
// try {
println "babbles"
callRealMethod()
println "bubbles"
// }catch( Exception e ) {
// println "exception $e"
// }
}
}
CUT spyCut = Spy( CUT ){
compileOutputLines(_) >> { TopDocs mockTD ->
println "babbles 2"
callRealMethod()
println "bubbles 2"
}
}
when:
stubCut.compileOutputLines( Mock( TopDocs ))
spyCut.compileOutputLines( Mock( TopDocs ))
then:
true
}
输出到stdout
:
babbles
bubbles
babbles 2
gubbins
bubbles 2
我试图在网上找到 link 完整的 Spock Framework Javadoc...但我找不到..."non-framework" Javadoc 是 here,但是您不会在索引中找到方法 callRealMethod
。
从我从源码本地生成的JavadocAPI中,确实可以找到这个方法:是org.spockframework.mock.IMockInvocation
的一个方法。它说:
java.lang.Object callRealMethod()
Delegates this method invocation to the real object underlying this mock object, including any method arguments. If this mock object has no underlying real object, a CannotInvokeRealMethodException is thrown.
Returns: the return value of the method to which this invocation was delegated
我的理解(事实上)是 Stub 应该导致此 Exception
被抛出。但它似乎不是。路过的专家有什么意见吗?
前言
这是一个有趣的问题。理论上我的答案是:
callRealMethod()
is only available for spies, not for mocks or stubs. It is also only mentioned in the chapter about spies, did you notice?Think about it: A spy wraps a real object, so there you have a reference to a real method you can call. The same is not true for mocks and stubs which are just no-op subclasses. If you could call a real method for a stub, it would be a spy.
谜题
实际上,在我自己使用 Spock 1.1 (Groovy 2.4) 进行的测试中,我看到了与您不同甚至更奇怪的行为:无论我使用 mock、stub 还是 spy,callRealMethod()
总是调用真正的方法。这真是一个惊喜。所以,是的,这种行为与我的预期不同。在调试时查看接口实现的源代码,我也看不到任何对模拟对象类型的检查(是否是间谍?)。真正的方法只是识别和调用。
解决方案
查看 class DynamicProxyMockInterceptorAdapter
我找到了对此行为的解释:IMockInvocation
Javadoc 中提到的异常仅在尝试调用接口类型的真实方法时抛出模拟,从不用于模拟或 class 类型的对象:
public Object invoke(Object target, Method method, Object[] arguments) throws Throwable {
IResponseGenerator realMethodInvoker = (ReflectionUtil.isDefault(method) || ReflectionUtil.isObjectMethod(method))
? new DefaultMethodInvoker(target, method, arguments)
: new FailingRealMethodInvoker("Cannot invoke real method '" + method.getName() + "' on interface based mock object");
return interceptor.intercept(target, method, arguments, realMethodInvoker);
}
所以句子“如果这个模拟对象没有底层的真实对象,抛出一个(...)异常”在本质上是正确的,但有歧义,因为它没有解释“底层真实对象”的含义。你的假设是错误的,我的也是。我们俩都吸取了教训。
现在您什么时候会看到所描述的行为?
package de.scrum_master.Whosebug;
public interface MyInterface {
void doSomething();
}
package de.scrum_master.Whosebug
import org.spockframework.mock.CannotInvokeRealMethodException
import spock.lang.Specification
class MyInterfaceTest extends Specification {
def "Try to call real method on interface mock"() {
given:
MyInterface myInterface = Mock() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
def "Try to call real method on interface stub"() {
given:
MyInterface myInterface = Stub() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
def "Try to call real method on interface spy"() {
given:
MyInterface myInterface = Spy() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
}
更新: 我刚刚创建了 issue #830 请求改进 Spock 的文档。