You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何Mock静态方法RandomStringUtils.random?Groovy+Spock测试遇阻

问题:Spock测试中无法Mock静态方法RandomStringUtils.random

环境

  • Groovy 2.4
  • Spock 1.3-groovy-2.4

目标代码(vars/random.groovy)

#!/usr/bin/env groovy

import groovy.transform.Field
import org.apache.commons.lang.RandomStringUtils

@Field Random random_gen = new Random()
@Field char[] charset = 'abcdefghijklmnopqrstuvwxyz1234567890'.toCharArray()

String string_of_length(Integer length) {
    return RandomStringUtils.random(length, this.charset)
}

Integer bound_int(Integer lower, Integer upper) {
    return random_gen.nextInt() % (upper - lower) + lower
}

return this

失败的测试代码(test/randomSpec.groovy)

import spock.lang.Specification
import groovy.lang.GroovyShell;
import java.io.File;
import org.apache.commons.lang.RandomStringUtils;
import groovy.transform.Field

/*
    given:
    Palette palette = Stub()
    palette.getPrimaryColour() >> Colour.Red
    def renderer = new Renderer(palette)

    expect:
    renderer.getForegroundColour() == Colour.Red
 */
@Field def random = evaluate(new File('vars/random.groovy'))

class RandomSpec extends Specification {

    def "test string_of_length"() {
        given:
        GroovySpy(RandomStringUtils, global: true)
        RandomStringUtils.random(_ as int, _ as char[]) >> "abc"
        //def random = new GroovyShell().parse(new File('vars/random.groovy'))
        //File sourceFile = new File('vars/random.groovy')
        //def random = new GroovyClassLoader(getClass().getClassLoader()).parseClass(sourceFile).newInstance()

        when:
        def result = random.string_of_length(3)

        then:
        1*RandomStringUtils.random(3, random.charset)
        "abc" == result
    }
    def "test string_of_length 2"() {
        given:
        def random = new GroovyShell().parse(new File('vars/random.groovy'))
        RandomStringUtils mock = Mock()

        when:
        assert "abd" == random.string_of_length(3)

        then:
        mock.random(3, 'abcdefghijklmnopqrstuvwxyz1234567890'.toCharArray()) >> "abc"
    }
}

可行的Groovy控制台测试示例

class RandomSpec extends Specification {

    def "test string_of_length"() {
        given:
        GroovySpy(RandomStringUtils, global: true)
        def random2 = new GroovyShell().parse(new File('vars/random.groovy'))

        when:
        def result = random2.string_of_length(3)

        then:
        1*RandomStringUtils.random(3, random2.charset) >> "abc"
        "abc" == result
    }
}

失败原因分析

  • 提前加载实例导致Mock失效:测试类外部的@Field def random = evaluate(...)会在Spock初始化全局Mock之前就加载了random.groovy实例,此时该实例已经绑定了真实的RandomStringUtils静态方法,后续的GroovySpy无法拦截这个已加载实例的调用。
  • Stub语法位置错误:第一个测试中,在given块单独定义RandomStringUtils.random(_ as int, _ as char[]) >> "abc"不符合Spock规则,静态方法的Stub返回值必须和调用次数验证一起写在then块中。
  • Mock对象未关联目标代码:第二个测试中创建的RandomStringUtils Mock对象完全独立,目标代码的random实例并没有使用这个Mock,仍然调用真实静态方法,所以断言失败。

正确实现方式

修正后的测试代码如下,核心要点是在Mock之后再加载目标实例,并正确使用Spock的交互验证语法:

import spock.lang.Specification
import groovy.lang.GroovyShell
import java.io.File
import org.apache.commons.lang.RandomStringUtils

class RandomSpec extends Specification {

    def "test string_of_length with mocked RandomStringUtils"() {
        given:
        // 先创建全局GroovySpy,拦截静态方法
        GroovySpy(RandomStringUtils, global: true)
        // 在Mock生效后再加载random实例
        def random = new GroovyShell().parse(new File('vars/random.groovy'))

        when:
        def result = random.string_of_length(3)

        then:
        // 同时验证调用次数和指定返回值
        1*RandomStringUtils.random(3, random.charset) >> "abc"
        result == "abc"
    }
}

额外优化建议

如果需要多次测试,可以把实例加载逻辑放到setup()方法中,但要确保setup()内先执行Mock再加载实例:

import spock.lang.Specification
import groovy.lang.GroovyShell
import java.io.File
import org.apache.commons.lang.RandomStringUtils

class RandomSpec extends Specification {
    def random

    def setup() {
        // 每次测试前先创建全局Mock
        GroovySpy(RandomStringUtils, global: true)
        // 再加载实例
        random = new GroovyShell().parse(new File('vars/random.groovy'))
    }

    def "test string_of_length returns mocked value"() {
        when:
        def result = random.string_of_length(3)

        then:
        1*RandomStringUtils.random(3, random.charset) >> "abc"
        result == "abc"
    }

    def "test string_of_length with different mocked value"() {
        when:
        def result = random.string_of_length(5)

        then:
        1*RandomStringUtils.random(5, random.charset) >> "xyz12"
        result == "xyz12"
    }
}

内容的提问来源于stack exchange,提问作者Nikolai Ehrhardt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 06:05:01