Running tests on different test environments using Spock

1.4k Views Asked by At

I am presently writing REST API tests using REST Client and Spock. I want to be able to run my tests on the different test environments. My test data varies between test environment, so need to use and specify different input data per environment. For a sample test below

class MathSpec extends Specification {
    def "maximum of two numbers"() {
        expect:
            Math.max(a, b) == c

       where:
            a | b || c
            1 | 3 || 3
    }
}

Am I able to specify different data tables for each environment?

class MathSpec extends Specification {
    def "maximum of two numbers"() {
         expect:
              Math.max(a, b) == c

         where:
              @TEST 
              a | b || c
              1 | 3 || 3

              @PROD
              a | b || c
              4 | 5 || 6

     }
 }

If not, what's the best way to approach this problem? Thanks.

2

There are 2 best solutions below

0
On

Spock has Multi-Variable Data Pipes to handle this. You can move the "logic" to a data-driven method.

class MathSpec extends Specification {

   def "maximum of two numbers"(int a, int b, int c) {
        expect:
        Math.max(a, b) == c

        where:
        [a, b, c] << getData()
    }

    def getData() {
        if (System.getProperty("Staging"))
            [ [1, 3, 3] ]
       else
            [ [3, 5, 5] ]
   }
}

In your case, I would use an abstract getData() method.
I would implement in getData() in two different classes MathProductionSpec and MathStagingSpec
I would use spock annotations to execute according to your environment.

example:

abstract class MathSpec extends Specification {

    def "maximum of two numbers"(int a, int b, int c) {
        expect:
        Math.max(a, b) == c

        where:
        [a, b, c] << getData()
    }

    def abstract getData()
}
@Production
class MathProductionSpec extends MathSpec {
    def getData() {
        [ [4, 2, 4] ]
    }
}
@Staging
class MathStagingSpec extends MathSpec {
    def getData() {
        [ [1, 3, 3] ]
    }
}
0
On

The easiest way that comes to my mind to achieve your expectation is to prepare the data before running test methods based on some condition (e.g. execution environment) and then use this predefined data in your tests. Consider following example:

import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll

class EnvBasedDataSpec extends Specification {
    @Shared
    static Map data = [:]

    def setupSpec() {
        /* If you want to pick variable from env variables, use:
         * System.getenv().getOrDefault('execution.environment', 'test')
         *
         * If you want to provide variable like -Dexecution.environment=test, use:
         * System.getProperty('execution.environment', 'test')
         */
        def executionEnvironment = System.getProperty('execution.environment', 'test')

        switch (executionEnvironment) {
            case 'test':
                data = [a: 1, b: 3, c: 3]
                break
            case 'prod':
                data = [a: 2, b: 4, c: 4]
                break
        }
    }

    @Unroll
    def "maximum of two numbers (#a, #b) == #c"() {
        expect:
        Math.max(a, b) == c

        where:
        a       | b         || c
        data.a  | data.b    || data.c
    }
}

In this example we prepare shared data that holds values we will use in tests. Here we are expecting that the execution environment information will be passed as

-Dexecution.environment=value 

property (alternatively you can pass the same information using env variables). In this example we also use the default value if given property is missing - test in this case (it will prevent test from failing if you forgot specifying your execution environment variable).

Alternative: @IgnoreIf conditional exection

Spock supports conditional execution. Take a look what the same test would look like if we use @IgnoreIf approach:

import spock.lang.IgnoreIf
import spock.lang.Specification

class AlternativeEnvBasedDataSpec extends Specification {

    @IgnoreIf({ System.getProperty('execution.environment') == 'prod' })
    def "maximum of two numbers (test)"() {
        expect:
        Math.max(a, b) == c

        where:
        a  | b  || c
        1  | 3  || 3
    }

    @IgnoreIf({ System.getProperty('execution.environment') == 'test' })
    def "maximum of two numbers (prod)"() {
        expect:
        Math.max(a, b) == c

        where:
        a  | b  || c
        2  | 4  || 4
    }
}

Unfortunately, this approach requires a lot of duplication: you have to duplicate test methods and you can't re-use the same name (compiler does not allow to do that). It's pretty error-prone - you have to pay attention to put the same test body to all methods that should test the same stuff, but with different data. Another thing is that condition passed to @IgnoreIf will have to be modified if you introduce 3rd environment - in this case you will specify it something like that:

@IgnoreIf({ !(System.getProperty('execution.environment') in ['staging', 'prod']) })

I guess you see how much problematic it starts to be.