Analog of TestNG data provider in Spock









up vote
0
down vote

favorite












I'm new to Spock, and currently switching to it, but I inherited plenty of test configuration files which need to be re-used. Each config file is a JSON, having the same name as Spec class. For each test method there is a list of maps with parameters, e.g.:



LoginSpec.json:

"My first test": [

"user": "user_one",
"role": "ADMIN"
,

"user": "user_two",
"role": "REPORTER",
"other_param": "other"

],

"Some Other Test Method": [

"url": "/lab1",
"button_name": "Show news popup"

]



TestNG allowed me to pass test method name in data provider method, so I could return the list of maps depending on test class name and test method name. I had only one data provider method in my base class:



public Object getData(String method) 
DataReader reader = new JsonReader()
return reader.parse(packageFullName, getClass().simpleName, method)



As a result of this method I get an array of Maps to use in each of test iteration. And then I just specify this method as a DataProvider:



@Test(dataProvider = "getData", priority = 1)
void EULA_1(Map data) <====
Pages.login.openLoginPage()
Pages.login.logIn(data.user) <====
...



This works perfectly: declared ones in the base class, it automatically receives the test and provides test data.



The question is: is there a way to apply similar approach in Spock tests?



I'd like to have some getData() method in my base class, where I'm able to read tests parameters depending on the test method name and then pass them into where block.



I tried to use my json reader as shown below:



def "My first test"() 
setup:
println(data)

when:
...
then:
...

where:
data = dataReader.parse("JobE2E", "LoginSpec.json", "My first test")



This example gives me required list of maps, but has two problems:




  1. data here - is the full list of maps, not one map for each iteration;

  2. I'm forced to explicitly type the name of test method, class etc.

Summing up:
What is the best way to implement a data provider which will receive test method name and return a list of maps?










share|improve this question

























    up vote
    0
    down vote

    favorite












    I'm new to Spock, and currently switching to it, but I inherited plenty of test configuration files which need to be re-used. Each config file is a JSON, having the same name as Spec class. For each test method there is a list of maps with parameters, e.g.:



    LoginSpec.json:

    "My first test": [

    "user": "user_one",
    "role": "ADMIN"
    ,

    "user": "user_two",
    "role": "REPORTER",
    "other_param": "other"

    ],

    "Some Other Test Method": [

    "url": "/lab1",
    "button_name": "Show news popup"

    ]



    TestNG allowed me to pass test method name in data provider method, so I could return the list of maps depending on test class name and test method name. I had only one data provider method in my base class:



    public Object getData(String method) 
    DataReader reader = new JsonReader()
    return reader.parse(packageFullName, getClass().simpleName, method)



    As a result of this method I get an array of Maps to use in each of test iteration. And then I just specify this method as a DataProvider:



    @Test(dataProvider = "getData", priority = 1)
    void EULA_1(Map data) <====
    Pages.login.openLoginPage()
    Pages.login.logIn(data.user) <====
    ...



    This works perfectly: declared ones in the base class, it automatically receives the test and provides test data.



    The question is: is there a way to apply similar approach in Spock tests?



    I'd like to have some getData() method in my base class, where I'm able to read tests parameters depending on the test method name and then pass them into where block.



    I tried to use my json reader as shown below:



    def "My first test"() 
    setup:
    println(data)

    when:
    ...
    then:
    ...

    where:
    data = dataReader.parse("JobE2E", "LoginSpec.json", "My first test")



    This example gives me required list of maps, but has two problems:




    1. data here - is the full list of maps, not one map for each iteration;

    2. I'm forced to explicitly type the name of test method, class etc.

    Summing up:
    What is the best way to implement a data provider which will receive test method name and return a list of maps?










    share|improve this question























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite











      I'm new to Spock, and currently switching to it, but I inherited plenty of test configuration files which need to be re-used. Each config file is a JSON, having the same name as Spec class. For each test method there is a list of maps with parameters, e.g.:



      LoginSpec.json:

      "My first test": [

      "user": "user_one",
      "role": "ADMIN"
      ,

      "user": "user_two",
      "role": "REPORTER",
      "other_param": "other"

      ],

      "Some Other Test Method": [

      "url": "/lab1",
      "button_name": "Show news popup"

      ]



      TestNG allowed me to pass test method name in data provider method, so I could return the list of maps depending on test class name and test method name. I had only one data provider method in my base class:



      public Object getData(String method) 
      DataReader reader = new JsonReader()
      return reader.parse(packageFullName, getClass().simpleName, method)



      As a result of this method I get an array of Maps to use in each of test iteration. And then I just specify this method as a DataProvider:



      @Test(dataProvider = "getData", priority = 1)
      void EULA_1(Map data) <====
      Pages.login.openLoginPage()
      Pages.login.logIn(data.user) <====
      ...



      This works perfectly: declared ones in the base class, it automatically receives the test and provides test data.



      The question is: is there a way to apply similar approach in Spock tests?



      I'd like to have some getData() method in my base class, where I'm able to read tests parameters depending on the test method name and then pass them into where block.



      I tried to use my json reader as shown below:



      def "My first test"() 
      setup:
      println(data)

      when:
      ...
      then:
      ...

      where:
      data = dataReader.parse("JobE2E", "LoginSpec.json", "My first test")



      This example gives me required list of maps, but has two problems:




      1. data here - is the full list of maps, not one map for each iteration;

      2. I'm forced to explicitly type the name of test method, class etc.

      Summing up:
      What is the best way to implement a data provider which will receive test method name and return a list of maps?










      share|improve this question













      I'm new to Spock, and currently switching to it, but I inherited plenty of test configuration files which need to be re-used. Each config file is a JSON, having the same name as Spec class. For each test method there is a list of maps with parameters, e.g.:



      LoginSpec.json:

      "My first test": [

      "user": "user_one",
      "role": "ADMIN"
      ,

      "user": "user_two",
      "role": "REPORTER",
      "other_param": "other"

      ],

      "Some Other Test Method": [

      "url": "/lab1",
      "button_name": "Show news popup"

      ]



      TestNG allowed me to pass test method name in data provider method, so I could return the list of maps depending on test class name and test method name. I had only one data provider method in my base class:



      public Object getData(String method) 
      DataReader reader = new JsonReader()
      return reader.parse(packageFullName, getClass().simpleName, method)



      As a result of this method I get an array of Maps to use in each of test iteration. And then I just specify this method as a DataProvider:



      @Test(dataProvider = "getData", priority = 1)
      void EULA_1(Map data) <====
      Pages.login.openLoginPage()
      Pages.login.logIn(data.user) <====
      ...



      This works perfectly: declared ones in the base class, it automatically receives the test and provides test data.



      The question is: is there a way to apply similar approach in Spock tests?



      I'd like to have some getData() method in my base class, where I'm able to read tests parameters depending on the test method name and then pass them into where block.



      I tried to use my json reader as shown below:



      def "My first test"() 
      setup:
      println(data)

      when:
      ...
      then:
      ...

      where:
      data = dataReader.parse("JobE2E", "LoginSpec.json", "My first test")



      This example gives me required list of maps, but has two problems:




      1. data here - is the full list of maps, not one map for each iteration;

      2. I'm forced to explicitly type the name of test method, class etc.

      Summing up:
      What is the best way to implement a data provider which will receive test method name and return a list of maps?







      java testing groovy testng spock






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 6 at 13:45









      BohdanN

      199118




      199118






















          3 Answers
          3






          active

          oldest

          votes

















          up vote
          1
          down vote













          You can solve problem with data using this approach:



          data << dataReader.parse('JobE2E', "$getClass().name.json", 'My first test')


          It will iterate the list of maps so each test iteration will be parametrized only by that map.




          Current test name can be obtained by:



          specificationContext.currentFeature.name


          And current iteration name by:



          specificationContext.currentIteration.name


          But both are not accessible in the where section because it is executed before the test itself where only values from shared context are available. So here I'm afraid that you have to enter the test name manually.



          Update: I found solution how to get feature name in where section for you. It is realized by an own extension using interceptor.



          Feature details container:



          class FeatureDetails 
          String name



          Extension annotation:



          import org.spockframework.runtime.extension.ExtensionAnnotation

          import java.lang.annotation.ElementType
          import java.lang.annotation.Retention
          import java.lang.annotation.RetentionPolicy
          import java.lang.annotation.Target

          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.METHOD)
          @ExtensionAnnotation(FeatureDetailsExtension.class)
          @interface ShareFeatureDetails



          Spock extension with inline interceptor implementation:



          import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
          import org.spockframework.runtime.model.FeatureInfo

          class FeatureDetailsExtension extends AbstractAnnotationDrivenExtension<ShareFeatureDetails>
          def featureDetails = new FeatureDetails()

          @Override
          void visitFeatureAnnotation(ShareFeatureDetails annotation, FeatureInfo feature)
          feature.addInterceptor( i ->
          featureDetails.name = feature.name
          feature.spec.allFields.each f ->
          if (f.type == FeatureDetails.class && f.readValue(i.getInstance()) == null)
          f.writeValue(i.getInstance(), featureDetails)


          i.proceed()
          )




          Example usage of the extension:



          class DataProviderSpec extends Specification 
          @Shared
          FeatureDetails currentFeature

          @Unroll("Test #data.a * 2 = #data.b")
          @ShareFeatureDetails
          def 'test'()
          when:
          println data

          then:
          data.a * 2 == data.b

          where:
          data << loadData()


          @Unroll("Test #data.a * 3 = #data.b")
          @ShareFeatureDetails
          def 'another test'()
          when:
          println data

          then:
          data.a * 3 == data.b

          where:
          data << loadData()


          def loadData()
          // this is hard coded example
          println "$getClass().name.$currentFeature.name"
          if ('test' == currentFeature.name) return [[a: 1, b: 2], [a: 2, b: 4]]
          if ('another test' == currentFeature.name) return [[a: 3, b: 9], [a: 4, b: 12]]
          return
          // ... use load from data file (JSON, YAML, XML, ...) instead:
          // return dataReader.parse("$getClass().name.json", currentFeature.name)




          And the output of above example:




          DataProviderSpec.test

          [a:1, b:2]

          [a:2, b:4]

          DataProviderSpec.another test

          [a:3, b:6]

          [a:4, b:8]




          First idea was to use only annotated String featureName field in the spec class but there is a problem where visitFeatureAnnotation() method works with different spec instance during each call while loadData() method is executed each time on the first instance.




          Note: You can also add description with values specific to the current iteration using @Unroll annotation. For example:



          @Unroll("Test #data.a * 2 = #data.b")
          def 'test'()
          setup:
          ...
          when:
          ...
          then:
          data.a * 2 == data.b

          where:
          data << getData('test')


          def getData(String methodName)
          if ('test' == methodName) return [[a: 1, b: 2], [a: 2, b: 4]]
          ...



          Will produce:




          Test 1 * 2 = 2

          Test 2 * 2 = 4







          share|improve this answer






















          • Yep, thank you. This is very similar to what I have now: getData(Stirng name) method in base spec class, where I get class name and package. But I still forced to duplicate spec name in getData when calling it in where block. Really no change to get spec name there?
            – BohdanN
            Nov 12 at 10:53










          • I found solution how to get feature name in the where section and updated the answer accordingly. It is realized by a simple own Spock extension. I hope it will help to you.
            – cgrim
            Nov 12 at 13:30










          • Maybe I misunderstood something, but this solution suggests to list all the specs' names in dataprovider, in order to use if statements. This is still code duplication.
            – BohdanN
            Nov 12 at 14:06










          • loadData() method is only example of how it works - you can adapt it on your case and load data from JSON file instead of using if statements. So instead of loadData() you can use your: dataReader.parse("JobE2E", "$getClass().name.json", currentFeature.name)
            – cgrim
            Nov 12 at 14:10










          • I read again your answer. Thank you, will try your extension!
            – BohdanN
            Nov 12 at 14:23

















          up vote
          0
          down vote













          You could use JsonSlurper. It basically parses JSON and returns an Object which could be a List or a Map (just cast it). You can easily use it in your were block (remember to only use static or @Shared in there).



          Here is some documentation about JSON in Groovy.






          share|improve this answer




















          • Hi Michael, thanks for the answer, but it's not a question of how to parse JSON. I actually have a JSON parser which works okay for me and returns a map with params (I use Gson instead of JsonSlurper but no matter here). My question is how to implement passing required spec info into the parser. Does Spock have any kind of pre-test hook where I can get test's name and load the params into list of maps and then put this list into where?
            – BohdanN
            Nov 9 at 10:40


















          up vote
          0
          down vote













          Solved.



          The following method, declared in the BaseSpec class, gets the name of current spec automatically on a stage of where block and load params from a config file accordingly:



           protected List<Map<String, Object>> getData() 
          String methodName = StackTraceUtils.sanitize(new Throwable()).stackTrace[1].methodName
          FeatureInfo spec = specificationContext.currentSpec.features.find
          FeatureInfo info ->
          info.dataProviders.any
          it.dataProviderMethod.name == methodName


          Class className = getClass()
          String packageFullName = className.package.name
          String packageName = packageFullName[(packageFullName.lastIndexOf(".") + 1)..-1]
          TestDataReader reader = new JsonReader()
          return reader.parse(packageName, className.simpleName, spec.name)



          Usage in class, which is a subclass of BaseSpec class:



          def "My custom name spec"() 

          when:
          ...

          then:
          ...

          where:
          data << getData()






          share|improve this answer




















            Your Answer






            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "1"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader:
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            ,
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );













             

            draft saved


            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53173193%2fanalog-of-testng-data-provider-in-spock%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown

























            3 Answers
            3






            active

            oldest

            votes








            3 Answers
            3






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            1
            down vote













            You can solve problem with data using this approach:



            data << dataReader.parse('JobE2E', "$getClass().name.json", 'My first test')


            It will iterate the list of maps so each test iteration will be parametrized only by that map.




            Current test name can be obtained by:



            specificationContext.currentFeature.name


            And current iteration name by:



            specificationContext.currentIteration.name


            But both are not accessible in the where section because it is executed before the test itself where only values from shared context are available. So here I'm afraid that you have to enter the test name manually.



            Update: I found solution how to get feature name in where section for you. It is realized by an own extension using interceptor.



            Feature details container:



            class FeatureDetails 
            String name



            Extension annotation:



            import org.spockframework.runtime.extension.ExtensionAnnotation

            import java.lang.annotation.ElementType
            import java.lang.annotation.Retention
            import java.lang.annotation.RetentionPolicy
            import java.lang.annotation.Target

            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.METHOD)
            @ExtensionAnnotation(FeatureDetailsExtension.class)
            @interface ShareFeatureDetails



            Spock extension with inline interceptor implementation:



            import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
            import org.spockframework.runtime.model.FeatureInfo

            class FeatureDetailsExtension extends AbstractAnnotationDrivenExtension<ShareFeatureDetails>
            def featureDetails = new FeatureDetails()

            @Override
            void visitFeatureAnnotation(ShareFeatureDetails annotation, FeatureInfo feature)
            feature.addInterceptor( i ->
            featureDetails.name = feature.name
            feature.spec.allFields.each f ->
            if (f.type == FeatureDetails.class && f.readValue(i.getInstance()) == null)
            f.writeValue(i.getInstance(), featureDetails)


            i.proceed()
            )




            Example usage of the extension:



            class DataProviderSpec extends Specification 
            @Shared
            FeatureDetails currentFeature

            @Unroll("Test #data.a * 2 = #data.b")
            @ShareFeatureDetails
            def 'test'()
            when:
            println data

            then:
            data.a * 2 == data.b

            where:
            data << loadData()


            @Unroll("Test #data.a * 3 = #data.b")
            @ShareFeatureDetails
            def 'another test'()
            when:
            println data

            then:
            data.a * 3 == data.b

            where:
            data << loadData()


            def loadData()
            // this is hard coded example
            println "$getClass().name.$currentFeature.name"
            if ('test' == currentFeature.name) return [[a: 1, b: 2], [a: 2, b: 4]]
            if ('another test' == currentFeature.name) return [[a: 3, b: 9], [a: 4, b: 12]]
            return
            // ... use load from data file (JSON, YAML, XML, ...) instead:
            // return dataReader.parse("$getClass().name.json", currentFeature.name)




            And the output of above example:




            DataProviderSpec.test

            [a:1, b:2]

            [a:2, b:4]

            DataProviderSpec.another test

            [a:3, b:6]

            [a:4, b:8]




            First idea was to use only annotated String featureName field in the spec class but there is a problem where visitFeatureAnnotation() method works with different spec instance during each call while loadData() method is executed each time on the first instance.




            Note: You can also add description with values specific to the current iteration using @Unroll annotation. For example:



            @Unroll("Test #data.a * 2 = #data.b")
            def 'test'()
            setup:
            ...
            when:
            ...
            then:
            data.a * 2 == data.b

            where:
            data << getData('test')


            def getData(String methodName)
            if ('test' == methodName) return [[a: 1, b: 2], [a: 2, b: 4]]
            ...



            Will produce:




            Test 1 * 2 = 2

            Test 2 * 2 = 4







            share|improve this answer






















            • Yep, thank you. This is very similar to what I have now: getData(Stirng name) method in base spec class, where I get class name and package. But I still forced to duplicate spec name in getData when calling it in where block. Really no change to get spec name there?
              – BohdanN
              Nov 12 at 10:53










            • I found solution how to get feature name in the where section and updated the answer accordingly. It is realized by a simple own Spock extension. I hope it will help to you.
              – cgrim
              Nov 12 at 13:30










            • Maybe I misunderstood something, but this solution suggests to list all the specs' names in dataprovider, in order to use if statements. This is still code duplication.
              – BohdanN
              Nov 12 at 14:06










            • loadData() method is only example of how it works - you can adapt it on your case and load data from JSON file instead of using if statements. So instead of loadData() you can use your: dataReader.parse("JobE2E", "$getClass().name.json", currentFeature.name)
              – cgrim
              Nov 12 at 14:10










            • I read again your answer. Thank you, will try your extension!
              – BohdanN
              Nov 12 at 14:23














            up vote
            1
            down vote













            You can solve problem with data using this approach:



            data << dataReader.parse('JobE2E', "$getClass().name.json", 'My first test')


            It will iterate the list of maps so each test iteration will be parametrized only by that map.




            Current test name can be obtained by:



            specificationContext.currentFeature.name


            And current iteration name by:



            specificationContext.currentIteration.name


            But both are not accessible in the where section because it is executed before the test itself where only values from shared context are available. So here I'm afraid that you have to enter the test name manually.



            Update: I found solution how to get feature name in where section for you. It is realized by an own extension using interceptor.



            Feature details container:



            class FeatureDetails 
            String name



            Extension annotation:



            import org.spockframework.runtime.extension.ExtensionAnnotation

            import java.lang.annotation.ElementType
            import java.lang.annotation.Retention
            import java.lang.annotation.RetentionPolicy
            import java.lang.annotation.Target

            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.METHOD)
            @ExtensionAnnotation(FeatureDetailsExtension.class)
            @interface ShareFeatureDetails



            Spock extension with inline interceptor implementation:



            import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
            import org.spockframework.runtime.model.FeatureInfo

            class FeatureDetailsExtension extends AbstractAnnotationDrivenExtension<ShareFeatureDetails>
            def featureDetails = new FeatureDetails()

            @Override
            void visitFeatureAnnotation(ShareFeatureDetails annotation, FeatureInfo feature)
            feature.addInterceptor( i ->
            featureDetails.name = feature.name
            feature.spec.allFields.each f ->
            if (f.type == FeatureDetails.class && f.readValue(i.getInstance()) == null)
            f.writeValue(i.getInstance(), featureDetails)


            i.proceed()
            )




            Example usage of the extension:



            class DataProviderSpec extends Specification 
            @Shared
            FeatureDetails currentFeature

            @Unroll("Test #data.a * 2 = #data.b")
            @ShareFeatureDetails
            def 'test'()
            when:
            println data

            then:
            data.a * 2 == data.b

            where:
            data << loadData()


            @Unroll("Test #data.a * 3 = #data.b")
            @ShareFeatureDetails
            def 'another test'()
            when:
            println data

            then:
            data.a * 3 == data.b

            where:
            data << loadData()


            def loadData()
            // this is hard coded example
            println "$getClass().name.$currentFeature.name"
            if ('test' == currentFeature.name) return [[a: 1, b: 2], [a: 2, b: 4]]
            if ('another test' == currentFeature.name) return [[a: 3, b: 9], [a: 4, b: 12]]
            return
            // ... use load from data file (JSON, YAML, XML, ...) instead:
            // return dataReader.parse("$getClass().name.json", currentFeature.name)




            And the output of above example:




            DataProviderSpec.test

            [a:1, b:2]

            [a:2, b:4]

            DataProviderSpec.another test

            [a:3, b:6]

            [a:4, b:8]




            First idea was to use only annotated String featureName field in the spec class but there is a problem where visitFeatureAnnotation() method works with different spec instance during each call while loadData() method is executed each time on the first instance.




            Note: You can also add description with values specific to the current iteration using @Unroll annotation. For example:



            @Unroll("Test #data.a * 2 = #data.b")
            def 'test'()
            setup:
            ...
            when:
            ...
            then:
            data.a * 2 == data.b

            where:
            data << getData('test')


            def getData(String methodName)
            if ('test' == methodName) return [[a: 1, b: 2], [a: 2, b: 4]]
            ...



            Will produce:




            Test 1 * 2 = 2

            Test 2 * 2 = 4







            share|improve this answer






















            • Yep, thank you. This is very similar to what I have now: getData(Stirng name) method in base spec class, where I get class name and package. But I still forced to duplicate spec name in getData when calling it in where block. Really no change to get spec name there?
              – BohdanN
              Nov 12 at 10:53










            • I found solution how to get feature name in the where section and updated the answer accordingly. It is realized by a simple own Spock extension. I hope it will help to you.
              – cgrim
              Nov 12 at 13:30










            • Maybe I misunderstood something, but this solution suggests to list all the specs' names in dataprovider, in order to use if statements. This is still code duplication.
              – BohdanN
              Nov 12 at 14:06










            • loadData() method is only example of how it works - you can adapt it on your case and load data from JSON file instead of using if statements. So instead of loadData() you can use your: dataReader.parse("JobE2E", "$getClass().name.json", currentFeature.name)
              – cgrim
              Nov 12 at 14:10










            • I read again your answer. Thank you, will try your extension!
              – BohdanN
              Nov 12 at 14:23












            up vote
            1
            down vote










            up vote
            1
            down vote









            You can solve problem with data using this approach:



            data << dataReader.parse('JobE2E', "$getClass().name.json", 'My first test')


            It will iterate the list of maps so each test iteration will be parametrized only by that map.




            Current test name can be obtained by:



            specificationContext.currentFeature.name


            And current iteration name by:



            specificationContext.currentIteration.name


            But both are not accessible in the where section because it is executed before the test itself where only values from shared context are available. So here I'm afraid that you have to enter the test name manually.



            Update: I found solution how to get feature name in where section for you. It is realized by an own extension using interceptor.



            Feature details container:



            class FeatureDetails 
            String name



            Extension annotation:



            import org.spockframework.runtime.extension.ExtensionAnnotation

            import java.lang.annotation.ElementType
            import java.lang.annotation.Retention
            import java.lang.annotation.RetentionPolicy
            import java.lang.annotation.Target

            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.METHOD)
            @ExtensionAnnotation(FeatureDetailsExtension.class)
            @interface ShareFeatureDetails



            Spock extension with inline interceptor implementation:



            import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
            import org.spockframework.runtime.model.FeatureInfo

            class FeatureDetailsExtension extends AbstractAnnotationDrivenExtension<ShareFeatureDetails>
            def featureDetails = new FeatureDetails()

            @Override
            void visitFeatureAnnotation(ShareFeatureDetails annotation, FeatureInfo feature)
            feature.addInterceptor( i ->
            featureDetails.name = feature.name
            feature.spec.allFields.each f ->
            if (f.type == FeatureDetails.class && f.readValue(i.getInstance()) == null)
            f.writeValue(i.getInstance(), featureDetails)


            i.proceed()
            )




            Example usage of the extension:



            class DataProviderSpec extends Specification 
            @Shared
            FeatureDetails currentFeature

            @Unroll("Test #data.a * 2 = #data.b")
            @ShareFeatureDetails
            def 'test'()
            when:
            println data

            then:
            data.a * 2 == data.b

            where:
            data << loadData()


            @Unroll("Test #data.a * 3 = #data.b")
            @ShareFeatureDetails
            def 'another test'()
            when:
            println data

            then:
            data.a * 3 == data.b

            where:
            data << loadData()


            def loadData()
            // this is hard coded example
            println "$getClass().name.$currentFeature.name"
            if ('test' == currentFeature.name) return [[a: 1, b: 2], [a: 2, b: 4]]
            if ('another test' == currentFeature.name) return [[a: 3, b: 9], [a: 4, b: 12]]
            return
            // ... use load from data file (JSON, YAML, XML, ...) instead:
            // return dataReader.parse("$getClass().name.json", currentFeature.name)




            And the output of above example:




            DataProviderSpec.test

            [a:1, b:2]

            [a:2, b:4]

            DataProviderSpec.another test

            [a:3, b:6]

            [a:4, b:8]




            First idea was to use only annotated String featureName field in the spec class but there is a problem where visitFeatureAnnotation() method works with different spec instance during each call while loadData() method is executed each time on the first instance.




            Note: You can also add description with values specific to the current iteration using @Unroll annotation. For example:



            @Unroll("Test #data.a * 2 = #data.b")
            def 'test'()
            setup:
            ...
            when:
            ...
            then:
            data.a * 2 == data.b

            where:
            data << getData('test')


            def getData(String methodName)
            if ('test' == methodName) return [[a: 1, b: 2], [a: 2, b: 4]]
            ...



            Will produce:




            Test 1 * 2 = 2

            Test 2 * 2 = 4







            share|improve this answer














            You can solve problem with data using this approach:



            data << dataReader.parse('JobE2E', "$getClass().name.json", 'My first test')


            It will iterate the list of maps so each test iteration will be parametrized only by that map.




            Current test name can be obtained by:



            specificationContext.currentFeature.name


            And current iteration name by:



            specificationContext.currentIteration.name


            But both are not accessible in the where section because it is executed before the test itself where only values from shared context are available. So here I'm afraid that you have to enter the test name manually.



            Update: I found solution how to get feature name in where section for you. It is realized by an own extension using interceptor.



            Feature details container:



            class FeatureDetails 
            String name



            Extension annotation:



            import org.spockframework.runtime.extension.ExtensionAnnotation

            import java.lang.annotation.ElementType
            import java.lang.annotation.Retention
            import java.lang.annotation.RetentionPolicy
            import java.lang.annotation.Target

            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.METHOD)
            @ExtensionAnnotation(FeatureDetailsExtension.class)
            @interface ShareFeatureDetails



            Spock extension with inline interceptor implementation:



            import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
            import org.spockframework.runtime.model.FeatureInfo

            class FeatureDetailsExtension extends AbstractAnnotationDrivenExtension<ShareFeatureDetails>
            def featureDetails = new FeatureDetails()

            @Override
            void visitFeatureAnnotation(ShareFeatureDetails annotation, FeatureInfo feature)
            feature.addInterceptor( i ->
            featureDetails.name = feature.name
            feature.spec.allFields.each f ->
            if (f.type == FeatureDetails.class && f.readValue(i.getInstance()) == null)
            f.writeValue(i.getInstance(), featureDetails)


            i.proceed()
            )




            Example usage of the extension:



            class DataProviderSpec extends Specification 
            @Shared
            FeatureDetails currentFeature

            @Unroll("Test #data.a * 2 = #data.b")
            @ShareFeatureDetails
            def 'test'()
            when:
            println data

            then:
            data.a * 2 == data.b

            where:
            data << loadData()


            @Unroll("Test #data.a * 3 = #data.b")
            @ShareFeatureDetails
            def 'another test'()
            when:
            println data

            then:
            data.a * 3 == data.b

            where:
            data << loadData()


            def loadData()
            // this is hard coded example
            println "$getClass().name.$currentFeature.name"
            if ('test' == currentFeature.name) return [[a: 1, b: 2], [a: 2, b: 4]]
            if ('another test' == currentFeature.name) return [[a: 3, b: 9], [a: 4, b: 12]]
            return
            // ... use load from data file (JSON, YAML, XML, ...) instead:
            // return dataReader.parse("$getClass().name.json", currentFeature.name)




            And the output of above example:




            DataProviderSpec.test

            [a:1, b:2]

            [a:2, b:4]

            DataProviderSpec.another test

            [a:3, b:6]

            [a:4, b:8]




            First idea was to use only annotated String featureName field in the spec class but there is a problem where visitFeatureAnnotation() method works with different spec instance during each call while loadData() method is executed each time on the first instance.




            Note: You can also add description with values specific to the current iteration using @Unroll annotation. For example:



            @Unroll("Test #data.a * 2 = #data.b")
            def 'test'()
            setup:
            ...
            when:
            ...
            then:
            data.a * 2 == data.b

            where:
            data << getData('test')


            def getData(String methodName)
            if ('test' == methodName) return [[a: 1, b: 2], [a: 2, b: 4]]
            ...



            Will produce:




            Test 1 * 2 = 2

            Test 2 * 2 = 4








            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited Nov 12 at 14:18

























            answered Nov 9 at 21:13









            cgrim

            1,1181418




            1,1181418











            • Yep, thank you. This is very similar to what I have now: getData(Stirng name) method in base spec class, where I get class name and package. But I still forced to duplicate spec name in getData when calling it in where block. Really no change to get spec name there?
              – BohdanN
              Nov 12 at 10:53










            • I found solution how to get feature name in the where section and updated the answer accordingly. It is realized by a simple own Spock extension. I hope it will help to you.
              – cgrim
              Nov 12 at 13:30










            • Maybe I misunderstood something, but this solution suggests to list all the specs' names in dataprovider, in order to use if statements. This is still code duplication.
              – BohdanN
              Nov 12 at 14:06










            • loadData() method is only example of how it works - you can adapt it on your case and load data from JSON file instead of using if statements. So instead of loadData() you can use your: dataReader.parse("JobE2E", "$getClass().name.json", currentFeature.name)
              – cgrim
              Nov 12 at 14:10










            • I read again your answer. Thank you, will try your extension!
              – BohdanN
              Nov 12 at 14:23
















            • Yep, thank you. This is very similar to what I have now: getData(Stirng name) method in base spec class, where I get class name and package. But I still forced to duplicate spec name in getData when calling it in where block. Really no change to get spec name there?
              – BohdanN
              Nov 12 at 10:53










            • I found solution how to get feature name in the where section and updated the answer accordingly. It is realized by a simple own Spock extension. I hope it will help to you.
              – cgrim
              Nov 12 at 13:30










            • Maybe I misunderstood something, but this solution suggests to list all the specs' names in dataprovider, in order to use if statements. This is still code duplication.
              – BohdanN
              Nov 12 at 14:06










            • loadData() method is only example of how it works - you can adapt it on your case and load data from JSON file instead of using if statements. So instead of loadData() you can use your: dataReader.parse("JobE2E", "$getClass().name.json", currentFeature.name)
              – cgrim
              Nov 12 at 14:10










            • I read again your answer. Thank you, will try your extension!
              – BohdanN
              Nov 12 at 14:23















            Yep, thank you. This is very similar to what I have now: getData(Stirng name) method in base spec class, where I get class name and package. But I still forced to duplicate spec name in getData when calling it in where block. Really no change to get spec name there?
            – BohdanN
            Nov 12 at 10:53




            Yep, thank you. This is very similar to what I have now: getData(Stirng name) method in base spec class, where I get class name and package. But I still forced to duplicate spec name in getData when calling it in where block. Really no change to get spec name there?
            – BohdanN
            Nov 12 at 10:53












            I found solution how to get feature name in the where section and updated the answer accordingly. It is realized by a simple own Spock extension. I hope it will help to you.
            – cgrim
            Nov 12 at 13:30




            I found solution how to get feature name in the where section and updated the answer accordingly. It is realized by a simple own Spock extension. I hope it will help to you.
            – cgrim
            Nov 12 at 13:30












            Maybe I misunderstood something, but this solution suggests to list all the specs' names in dataprovider, in order to use if statements. This is still code duplication.
            – BohdanN
            Nov 12 at 14:06




            Maybe I misunderstood something, but this solution suggests to list all the specs' names in dataprovider, in order to use if statements. This is still code duplication.
            – BohdanN
            Nov 12 at 14:06












            loadData() method is only example of how it works - you can adapt it on your case and load data from JSON file instead of using if statements. So instead of loadData() you can use your: dataReader.parse("JobE2E", "$getClass().name.json", currentFeature.name)
            – cgrim
            Nov 12 at 14:10




            loadData() method is only example of how it works - you can adapt it on your case and load data from JSON file instead of using if statements. So instead of loadData() you can use your: dataReader.parse("JobE2E", "$getClass().name.json", currentFeature.name)
            – cgrim
            Nov 12 at 14:10












            I read again your answer. Thank you, will try your extension!
            – BohdanN
            Nov 12 at 14:23




            I read again your answer. Thank you, will try your extension!
            – BohdanN
            Nov 12 at 14:23












            up vote
            0
            down vote













            You could use JsonSlurper. It basically parses JSON and returns an Object which could be a List or a Map (just cast it). You can easily use it in your were block (remember to only use static or @Shared in there).



            Here is some documentation about JSON in Groovy.






            share|improve this answer




















            • Hi Michael, thanks for the answer, but it's not a question of how to parse JSON. I actually have a JSON parser which works okay for me and returns a map with params (I use Gson instead of JsonSlurper but no matter here). My question is how to implement passing required spec info into the parser. Does Spock have any kind of pre-test hook where I can get test's name and load the params into list of maps and then put this list into where?
              – BohdanN
              Nov 9 at 10:40















            up vote
            0
            down vote













            You could use JsonSlurper. It basically parses JSON and returns an Object which could be a List or a Map (just cast it). You can easily use it in your were block (remember to only use static or @Shared in there).



            Here is some documentation about JSON in Groovy.






            share|improve this answer




















            • Hi Michael, thanks for the answer, but it's not a question of how to parse JSON. I actually have a JSON parser which works okay for me and returns a map with params (I use Gson instead of JsonSlurper but no matter here). My question is how to implement passing required spec info into the parser. Does Spock have any kind of pre-test hook where I can get test's name and load the params into list of maps and then put this list into where?
              – BohdanN
              Nov 9 at 10:40













            up vote
            0
            down vote










            up vote
            0
            down vote









            You could use JsonSlurper. It basically parses JSON and returns an Object which could be a List or a Map (just cast it). You can easily use it in your were block (remember to only use static or @Shared in there).



            Here is some documentation about JSON in Groovy.






            share|improve this answer












            You could use JsonSlurper. It basically parses JSON and returns an Object which could be a List or a Map (just cast it). You can easily use it in your were block (remember to only use static or @Shared in there).



            Here is some documentation about JSON in Groovy.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 9 at 6:53









            Michael

            1,0581712




            1,0581712











            • Hi Michael, thanks for the answer, but it's not a question of how to parse JSON. I actually have a JSON parser which works okay for me and returns a map with params (I use Gson instead of JsonSlurper but no matter here). My question is how to implement passing required spec info into the parser. Does Spock have any kind of pre-test hook where I can get test's name and load the params into list of maps and then put this list into where?
              – BohdanN
              Nov 9 at 10:40

















            • Hi Michael, thanks for the answer, but it's not a question of how to parse JSON. I actually have a JSON parser which works okay for me and returns a map with params (I use Gson instead of JsonSlurper but no matter here). My question is how to implement passing required spec info into the parser. Does Spock have any kind of pre-test hook where I can get test's name and load the params into list of maps and then put this list into where?
              – BohdanN
              Nov 9 at 10:40
















            Hi Michael, thanks for the answer, but it's not a question of how to parse JSON. I actually have a JSON parser which works okay for me and returns a map with params (I use Gson instead of JsonSlurper but no matter here). My question is how to implement passing required spec info into the parser. Does Spock have any kind of pre-test hook where I can get test's name and load the params into list of maps and then put this list into where?
            – BohdanN
            Nov 9 at 10:40





            Hi Michael, thanks for the answer, but it's not a question of how to parse JSON. I actually have a JSON parser which works okay for me and returns a map with params (I use Gson instead of JsonSlurper but no matter here). My question is how to implement passing required spec info into the parser. Does Spock have any kind of pre-test hook where I can get test's name and load the params into list of maps and then put this list into where?
            – BohdanN
            Nov 9 at 10:40











            up vote
            0
            down vote













            Solved.



            The following method, declared in the BaseSpec class, gets the name of current spec automatically on a stage of where block and load params from a config file accordingly:



             protected List<Map<String, Object>> getData() 
            String methodName = StackTraceUtils.sanitize(new Throwable()).stackTrace[1].methodName
            FeatureInfo spec = specificationContext.currentSpec.features.find
            FeatureInfo info ->
            info.dataProviders.any
            it.dataProviderMethod.name == methodName


            Class className = getClass()
            String packageFullName = className.package.name
            String packageName = packageFullName[(packageFullName.lastIndexOf(".") + 1)..-1]
            TestDataReader reader = new JsonReader()
            return reader.parse(packageName, className.simpleName, spec.name)



            Usage in class, which is a subclass of BaseSpec class:



            def "My custom name spec"() 

            when:
            ...

            then:
            ...

            where:
            data << getData()






            share|improve this answer
























              up vote
              0
              down vote













              Solved.



              The following method, declared in the BaseSpec class, gets the name of current spec automatically on a stage of where block and load params from a config file accordingly:



               protected List<Map<String, Object>> getData() 
              String methodName = StackTraceUtils.sanitize(new Throwable()).stackTrace[1].methodName
              FeatureInfo spec = specificationContext.currentSpec.features.find
              FeatureInfo info ->
              info.dataProviders.any
              it.dataProviderMethod.name == methodName


              Class className = getClass()
              String packageFullName = className.package.name
              String packageName = packageFullName[(packageFullName.lastIndexOf(".") + 1)..-1]
              TestDataReader reader = new JsonReader()
              return reader.parse(packageName, className.simpleName, spec.name)



              Usage in class, which is a subclass of BaseSpec class:



              def "My custom name spec"() 

              when:
              ...

              then:
              ...

              where:
              data << getData()






              share|improve this answer






















                up vote
                0
                down vote










                up vote
                0
                down vote









                Solved.



                The following method, declared in the BaseSpec class, gets the name of current spec automatically on a stage of where block and load params from a config file accordingly:



                 protected List<Map<String, Object>> getData() 
                String methodName = StackTraceUtils.sanitize(new Throwable()).stackTrace[1].methodName
                FeatureInfo spec = specificationContext.currentSpec.features.find
                FeatureInfo info ->
                info.dataProviders.any
                it.dataProviderMethod.name == methodName


                Class className = getClass()
                String packageFullName = className.package.name
                String packageName = packageFullName[(packageFullName.lastIndexOf(".") + 1)..-1]
                TestDataReader reader = new JsonReader()
                return reader.parse(packageName, className.simpleName, spec.name)



                Usage in class, which is a subclass of BaseSpec class:



                def "My custom name spec"() 

                when:
                ...

                then:
                ...

                where:
                data << getData()






                share|improve this answer












                Solved.



                The following method, declared in the BaseSpec class, gets the name of current spec automatically on a stage of where block and load params from a config file accordingly:



                 protected List<Map<String, Object>> getData() 
                String methodName = StackTraceUtils.sanitize(new Throwable()).stackTrace[1].methodName
                FeatureInfo spec = specificationContext.currentSpec.features.find
                FeatureInfo info ->
                info.dataProviders.any
                it.dataProviderMethod.name == methodName


                Class className = getClass()
                String packageFullName = className.package.name
                String packageName = packageFullName[(packageFullName.lastIndexOf(".") + 1)..-1]
                TestDataReader reader = new JsonReader()
                return reader.parse(packageName, className.simpleName, spec.name)



                Usage in class, which is a subclass of BaseSpec class:



                def "My custom name spec"() 

                when:
                ...

                then:
                ...

                where:
                data << getData()







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 12 at 14:17









                BohdanN

                199118




                199118



























                     

                    draft saved


                    draft discarded















































                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53173193%2fanalog-of-testng-data-provider-in-spock%23new-answer', 'question_page');

                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Use pre created SQLite database for Android project in kotlin

                    Darth Vader #20

                    Ondo