Spock 模拟验证返回 0 次调用

Posted

技术标签:

【中文标题】Spock 模拟验证返回 0 次调用【英文标题】:Spock mock verify returns 0 invocations 【发布时间】:2017-04-30 03:14:29 【问题描述】:

我正在开发一个包含 Camel 处理器类的 Spring Boot java 服务,如下所示:

public class MyProc implements Processor 

    @Autowired
    private LogService logService;

    public void process(Exchange e) 
            // exchange object processing
            logService.update(e)
           

我有以下 Spock 测试:

class MyProcTest extends Specification 
    @Shared def logService = Mock(LogService)
    @Shared def proc = new MyProc()

    def ctx = new DefaultCamelContext()
    def exch = new DefaultExchange(ctx)

    void setupSpec() 
        proc.logService = logService
    

    def "log is updated when valid exchange is processed"() 
        given:
            exch.getIn().setBody(//xml string set on body)
        when:
            proc.process(exch)
        then: 
            1 * logService.update(_)
    

当我运行它时,我遇到了失败,说明 1 * logService.update(_) 的调用太少(0 调用)。我尝试调试代码,在 MyProc 中,该语句被命中,并且 logService 对象在突出显示时(在 Eclipse 中)状态为“模拟名为 $spock_sharedField_logService 的类型 LogService”,因此看起来模拟已成功注入 MyProc 实例.

我是 Spock 和 Groovy 的新手,所以我可能会遗漏一些东西,但测试不应该通过吗?运行测试时正在调用 mocks 方法,所以我不明白为什么测试报告根本没有调用 mocks 方法。我是否在 MyProc 实例上初始化模拟/设置模拟/错误地设置交互?是否有一些我错过的 Groovy 功能​​或警告?据我了解,Spock 模拟将在调用时从方法返回默认值,这很好,我只想检查这个特定方法是否已作为 proc.process 的一部分被调用,并具有有效的交换对象

【问题讨论】:

一般来说,字段注入存在问题——尽可能使用构造函数注入。 如果你放弃@Shared会发生什么?看起来您正在实例化模拟 once (您通常按方法重置它)但每次都重建上下文。这种不匹配可能会导致问题。 放弃共享并切换到 void setup() vs setupSpec 工作,谢谢 另外我认为每个功能方法都会获得自己的实例字段副本,这不意味着每个测试都有新版本的 proc 和 logService 吗? @Shared 明确的意思是“不要为每个特征方法制作新的副本”。 【参考方案1】:

一个简单的答案是不要将@Shared 用于模拟/存根/间谍。

@Shared def proc = new MyProcessor()

def test() 
    given:
    def log = Mock(LogService)
    proc.logService = log

    when:
    proc.process(new Object())

    then:
    1 * log.update(_)

现在解释一下: 当您运行规范时,Spock 会为每个测试创建一个规范实例,此外,它还会创建一个共享规范实例来跟踪共享字段。

查看 org.spockframework.runtime.BaseSpecRunner 你会看到两个字段:

protected Specification sharedInstance;
protected Specification currentInstance;

此外,还有两种规范上下文:共享的和当前的。上下文保留实例、模拟上下文和模拟控制器。问题来了。当您将模拟声明为共享时,它会绑定到共享模拟控制器,但是当您声明交互('then:' 块)时,Spock 会将这些交互添加到当前模拟控制器。因此,当调用模拟方法时(例如 logService.update(e)),Spock 检查是否允许与共享模拟控制器进行此交互,因为您将模拟声明为共享但在那里什么也找不到,因为您将交互放在当前控制器中.

不要使用模拟/存根/间谍作为@Shared 字段。

【讨论】:

或者至少是 Spock 模拟。仍然有一些边缘情况(例如 Spring 上下文注入),您必须使用 Mockito 模拟。 抽搐

以上是关于Spock 模拟验证返回 0 次调用的主要内容,如果未能解决你的问题,请参考以下文章

Grails / Spock:如何在类中模拟从类本身调用方法的单个方法?

如何使用 Spring Boot 应用程序和 Spock 测试在运行时更改服务器端口

如何在 Spock 控制器测试中模拟 Grails 请求对象方法

SpringCloud升级之路2020.0.x版-40. spock 单元测试封装的 WebClient(下)

验证 Spock 中没有抛出异常

不能在验证参数中使用模拟函数调用:调用次数过多