Spock: Bug? Methods in "where" block evaluated before "setup" method call

Created on 3 Nov 2020  路  5Comments  路  Source: spockframework/spock

Issue description

Hello,

I'm facing the issue that variable binding in where block are evaluated before hooks before / setup which may override static variable that is being used in where block as method argument.

Is it expected to be like this or is it a bug?

How to reproduce

class TestClassShould extends Specification {

    private static String text  // null by default

    @Before
    void before() {
        println "called before"
        text = "some text entered"
    }

    void setup() {
        println "called setup"
        text = "some text entered"
    }

    def "should evaluate variables in where block after before hook"() {
        expect:
        println "ACTUAL value: $actual"
        actual != null

        where:
        actual    | _
        foo(text) | _    // evaluation of "message" variable happens before "before" / "setup" hooks
    }

    String foo(String str){
        println "evaluating foo($str)"
        str
    }

This test fails because it's not calling before or setup hooks before evaluating variables from where block.
Test output is following:

evaluating foo(null)
called before
called setup
ACTUAL value: null

Condition not satisfied:
actual != null
|      |
null   false

Additional Environment information

------------------------------------------------------------
Gradle 5.6.4
------------------------------------------------------------
Groovy:       2.5.4
Ant:          Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM:          11.0.8 (AdoptOpenJDK 11.0.8+10)
OS:           Windows 10 10.0 amd64

gradle: compile 'org.spockframework:spock-core:1.3-groovy-2.5'

We're using Spock with JUnit 4.

not a bug

Most helpful comment

That's actually expected.
The where block is extracted and evaluated before the tests run.
You also don't know how many iterations you would have.
Maybe you didn't notice, but if you duplicate your last line in the where block, even both foo calls are made before the first setup method.
The data table syntax you use is semantically identical to actual << [foo(text)] or in your example even actual = foo(text).

If you would use some lazy iterable as data provider instead of a simple list (for which the table syntax is just syntactic sugar),
that only evaluates the method when the next value is requested, the methods would not be executed all before the first setup invocation, but still each method would be invoked before the setup method for its iteration, because first the data provider has to provide the next value to see that there is actually another iteration to execute.

To get what you want you could for example delay the method invocation by wrapping it in a closure that you then call within the feature method invocation, for example like this:

def "should evaluate variables in where block after before hook"() {
    given:
    def actual = actualClosure()

    expect:
    println "ACTUAL value: $actual"
    actual != null

    where:
    _ | actualClosure       
    _ | { foo(text) }
}

All 5 comments

That's actually expected.
The where block is extracted and evaluated before the tests run.
You also don't know how many iterations you would have.
Maybe you didn't notice, but if you duplicate your last line in the where block, even both foo calls are made before the first setup method.
The data table syntax you use is semantically identical to actual << [foo(text)] or in your example even actual = foo(text).

If you would use some lazy iterable as data provider instead of a simple list (for which the table syntax is just syntactic sugar),
that only evaluates the method when the next value is requested, the methods would not be executed all before the first setup invocation, but still each method would be invoked before the setup method for its iteration, because first the data provider has to provide the next value to see that there is actually another iteration to execute.

To get what you want you could for example delay the method invocation by wrapping it in a closure that you then call within the feature method invocation, for example like this:

def "should evaluate variables in where block after before hook"() {
    given:
    def actual = actualClosure()

    expect:
    println "ACTUAL value: $actual"
    actual != null

    where:
    _ | actualClosure       
    _ | { foo(text) }
}

Two more things:

  1. @Before is a JUnit4 annotation and not used in Spock. The equivalent really is the setup() method.
  2. In your case the field is static. You can set a static or @Shared field in the setupSpec() method. In that case you do not need to use the closures @Vampire explained to you. You only need them if for whatever reason you really want to defer execution until after the setup() method has finished running, e.g. because your static field changes its value in between features (test methods) or iterations.

And not to forget, try to avoid static fields, better use the @Shared ones. :-)

Thank you for quick and detailed reply!

Using closure like { foo(bar) } in where: block for my use-case solves my problem in a more-less clean way.
Unfortunately, using @Shared variable for the described case doesn't help.

We can close this ticket :)

No, @Shared alone will not help, but it should almost always be preferred over static, that was a general advice. :-)

Was this page helpful?
0 / 5 - 0 ratings