Search code examples
gradlegroovyjenkins-groovyspock

How to mock random static method: RandomStringUtils.random


I have following easy file random.groovy in "vars" folder and Im trying to test with groovy 2.4 and spock testframework 1.3-groovy-2.4. The file is basically about generating random character sequences.

#!/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

And I try to mock or stub or spy the function RandomStringUtils.random, but nothing works, my tries, this file is under 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"
    }
}

And believe me: I read all the articles out there about mocking/stubbing/spying static methods. especially https://spockframework.org/spock/docs/1.2/all_in_one.html#_mocking_static_methods didnt really work, it seems a spy cannot define a return value for the stub, although its considered as a GlobalMock

I add for transparence the answer from groovy-console in vampires accepted answer:

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
    }
}

Solution

  • While as @kriegaex mentioned correctly, you should maybe not use a heavily outdated version of Spock, but at least use 2.3, the hint about usability of a global mock is wrong. That the class with the static method is a Java class is fine as long as the tested code is written in Groovy.

    Your problem is not that you cannot stub the static method, because you did.

    Your problem is that you try to split stubbing and mocking for the same call. You cannot define the return value in given and assert on the call count in then. When the method is called the first interaction that matches (with then preferred) is used and that is the one from then that does not specify a return value and thus uses the default which for a Spy is to call the real method.

    If you use 1 * RandomStringUtils.random(3, random.charset) >> "abc" in given or then, it works as you intended to (see example in Groovy web console)

    Have a look at the Combining Mocking and Stubbing chapter that explains it in more detail.