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

22,559

Solution 1

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)

Solution 2

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.

Solution 3

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.

Solution 4

Static fields should only be used for constants. Otherwise shared fields are preferable, because their semantics with respect to sharing are more well-defined.

Share:
22,559

Related videos on Youtube

topr
Author by

topr

Updated on February 19, 2021

Comments

  • topr
    topr about 3 years

    There is not much to add, the whole question is in the title.

    Consider these two instances of class Foo used in a Spock specification.

    @Shared Foo foo1 = new Foo()
    
    static Foo foo2 = new Foo()
    

    Overall, I know the idea behind @Shared annotation but I guess it's better to use language features, which in this case would be static field.

    Are there any specific cases in which one should preferred over the other or it's rather a matter of taste?

    • tim_yates
      tim_yates about 8 years
      Afaik they're effectively the same functionally, but @Shared better shows your intent
  • topr
    topr about 8 years
    Well, there is slight difference in having and not having 'then' block. Asserts are implicit on any non-void line in such a block. But agreed, the most value comes in readability. Thanks for the answer, I'm up-voting it but going to wait a bit more to see if any other will appear. I'm keen to see if there is any other no 'syntactic sugar' reason to prefer @Shared over static (or opposite).
  • topr
    topr over 7 years
    Interesting one, thanks. To sum up both answers: @Shared makes specification more readable and allows advantage of an instance based methods (like overriding).
  • Valya
    Valya over 6 years
    Instance variables are instantiated for each @Test case, no wonder, same for JUnit. Can you elaborate the last sentence? I don't get it.