Spock bug?存根上的callRealMethod()不会抛出预期的异常

时间:2018-03-10 19:01:37

标签: spock stub spy

从测试类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

  

喋喋不休
  泡泡
  bab呀2   格宾斯
  泡泡2

我试图在线找到完整的Spock Framework Javadoc链接......但我找不到它......“非框架”Javadoc是MaterialIcons,但你找不到索引中的方法callRealMethod

从我从源本地生成的Javadoc API,我确实可以找到这个方法:它是org.spockframework.mock.IMockInvocation的方法。它说:

  

java.lang.Object callRealMethod()

     

将此方法调用委托给底层的真实对象   模拟对象,包括任何方法参数。如果这个模拟对象有   没有底层真实对象,一个CannotInvokeRealMethodException   抛出。

     

返回:       委托此调用的方法的返回值

我的理解(例如它)是Stub应该导致这个Exception被抛出。但似乎并非如此。来自专家的任何评论?

1 个答案:

答案 0 :(得分:3)

前言

这是一个有趣的问题。从理论上讲,我的答案是:

  

callRealMethod()仅适用于间谍,不适用于模拟或存根。它也只在关于间谍的章节中提到过,你注意到了吗?

     

想一想:间谍包裹了一个真实的物体,所以你可以参考一个你可以调用的真实方法。对于只是无操作子类的模拟和存根,情况也是如此。如果你可以为一个存根调用一个真正的方法,它将是一个间谍。

谜题

实际上,我在Spock 1.1(Groovy 2.4)的测试中看到了与你的不同甚至更奇怪的行为:无论我使用模拟,存根还是间谍,callRealMethod() 总是< / strong>调用真正的方法。这真是一个惊喜。所以,是的,这种行为与我的预期不同。在调试时查看接口实现的源代码,我也看不到对mock对象类型的任何检查(它是间谍还是没有?)。真正的方法只是被识别和调用。

解决方案

查看类DynamicProxyMockInterceptorAdapter我找到了这种行为的解释:IMockInvocation Javadoc中提到的异常仅在尝试为接口类型mock调用实际方法时抛出,从不用于模拟或类类型对象:

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.stackoverflow;

public interface MyInterface {
  void doSomething();
}
package de.scrum_master.stackoverflow

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的文档。