Spock Groovy测试中,赋值给实例变量的Mock对象为null异常排查
Spock测试Jenkins CpsScript Mock触发NullPointerException问题排查
问题描述
用Spock对Groovy类做单元测试时,Mock了Jenkins CI的CpsScript类,测试中MyClass的实例变量ctx突然变为null,触发NullPointerException——但在测试的when阶段创建对象时,这个变量明明不为null,看起来Mock对象的引用被莫名清空了。
示例代码
测试类
import org.jenkinsci.plugins.workflow.cps.CpsScript import spock.lang.* class MySpec extends Specification { def 'does something'() { given: 'valid input' def foo = 'foo' when: 'something happens' def context = createContext() assert context def obj = new MyClass(createContext()) def success = obj.doSomething(foo) then: 'it is successful' assert success } private def createContext() { def ctx = Mock(CpsScript) ctx.sh(_) >> "sh called" return ctx } }
被测试类
class MyClass { private final def ctx MyClass(ctx) { this.ctx = ctx } def doSomething(foo) { ctx.sh('// bash script that uses foo') } }
问题原因
问题出在Spock Mock对象的作用域限制上:
Spock的Mock不是普通Groovy对象,是依赖测试方法上下文的动态代理。你在私有方法createContext()里创建Mock,这个方法的上下文和测试方法的闭包上下文不共享,当方法执行完毕栈帧销毁后,Spock会判定该Mock不再被测试上下文引用,直接回收它。所以当obj.doSomething()调用时,原本传入的Mock已经被销毁,ctx自然变成null。
解决方法
方法1:直接在测试方法上下文内创建Mock
把Mock的创建逻辑移到测试方法的given块里,确保Mock处于测试方法的上下文范围内:
import org.jenkinsci.plugins.workflow.cps.CpsScript import spock.lang.* class MySpec extends Specification { def 'does something'() { given: 'valid input' def foo = 'foo' // 在测试方法上下文内创建Mock def ctx = Mock(CpsScript) ctx.sh(_) >> "sh called" when: 'something happens' def obj = new MyClass(ctx) def success = obj.doSomething(foo) then: 'it is successful' success } }
方法2:用setup()方法创建Mock(适合复用逻辑)
如果需要在多个测试方法里复用Mock创建逻辑,可以把Mock放在setup()方法中创建——setup()会在每个测试方法执行前运行,其上下文和测试方法共享:
import org.jenkinsci.plugins.workflow.cps.CpsScript import spock.lang.* class MySpec extends Specification { private CpsScript ctx def setup() { // setup方法的上下文和测试方法共享,Mock不会被提前回收 ctx = Mock(CpsScript) ctx.sh(_) >> "sh called" } def 'does something'() { given: 'valid input' def foo = 'foo' when: 'something happens' def obj = new MyClass(ctx) def success = obj.doSomething(foo) then: 'it is successful' success } }
注意事项
- 不要在独立于测试方法上下文的私有方法里创建Spock Mock,避免被提前回收。
- 尽量不要用
@Shared注解标记Mock对象,会导致测试用例之间共享状态,破坏测试隔离性。
内容的提问来源于stack exchange,提问作者Boon
相关产品推荐
相关产品推荐

