When Spock's @Shared annotation should be preferred over a static field?

Spock is all about expressiveness and clarity.

Static is a Java keyword that only shows the internals of the class (that this field is the same for all instances)

@Shared is a Spock feature that says to the reader that this variable is the same for all feature methods. It is an instruction specifically for the unit test and makes the unit test more clear for the reader.

The same can be said for the main Spock blocks. If you think about it they do not really change anything on the code.

public void myScenario(){
  int a = 2 + 3;
  assertEquals(5,a);
}

public void "simple addition scenario"(){
  when: "I add two numbers"
    int a = 2 +3

  then: "I expect the correct result"
  a == 5
}

Both unit tests do exactly the same thing technically. The second however is showing more clearly the intention. The when: and then: labels do not really do anything with the code other than clarifying its intent.

So to sum up, @Shared is making the test more readable. (See also @Issue, @Title etc., they exist for the same purpose)


Contrary to JUnit, where you have to declare field variable static and assign value to it in

@BeforeClass
public static void setupClass()

so it was initialized just once per test suite (not each method), in Spock you may use instance field variable and annotate it with @Shared.

Consider the following example:

class SharedTestSpec extends spock.lang.Specification {

    @Shared
    def shared = shared()

    def shared() {
        "I came from ${this.class.simpleName}"
    }

    def 'Test one'() {
        given:
            println("test one, shared: $shared")
        expect: true
    }

    def 'Test two'() {
        given:
            println("test two, shared: $shared")
        expect: true

    }
}

class SubclassSpec extends SharedTestSpec {

    @Override
    def shared() {
        println("They've got me!")
        "I came from ${this.class.simpleName}"
    }
}

Running SubclassSpec gives you the following output:

test one, shared: I came from SubclassSpec
test two, shared: I came from SubclassSpec
They've got me!

Can't explain the print order though, but that's due to AST.


As a more exhaustive approach, here is a sample test with outputs:

@Unroll
class BasicSpec extends Specification {
    
    int initializedVariable
    
    int globalVariable = 200
    
    static int STATIC_VARIABLE = 300
    
    @Shared
    int sharedVariable = 400
    
    void setup() {
        initializedVariable = 100
    }
    
    void 'no changes'() {
        expect:
            printVariables()
            /*
            initializedVariable: 100
            globalVariable: 200
            STATIC_VARIABLE: 300
            sharedVariable: 400
             */
    }
    
    void 'change values'() {
        setup:
            initializedVariable = 1100
            globalVariable = 1200
            STATIC_VARIABLE = 1300
            sharedVariable = 1400
        
        expect:
            printVariables()
            /*
            initializedVariable: 1100
            globalVariable: 1200
            STATIC_VARIABLE: 1300
            sharedVariable: 1400
             */
    }
    
    void 'print values again'() {
        expect:
            printVariables()
            /*
            initializedVariable: 100
            globalVariable: 200
            STATIC_VARIABLE: 1300
            sharedVariable: 1400
             */
    }
    
    private void printVariables() {
        println "initializedVariable: $initializedVariable"
        println "globalVariable: $globalVariable"
        println "STATIC_VARIABLE: $STATIC_VARIABLE"
        println "sharedVariable: $sharedVariable\n"
    }
}

The surprising thing to me is that both the variable in the class' setup() method AS WELL as the global, instanced variable get reset on each test (presumably because the class is re-instantiated for each test case). Meanwhile, the static and the @Shared variable work as expected. As a result, the latter two are also able to be accessed in where clauses, which are run before some of the other ones that are listed prior in each test case.