JBehave
  1. JBehave
  2. JBEHAVE-922

Allow mapping of ExamplesTable rows to annotated custom types

    Details

    • Type: Improvement Improvement
    • Status: Resolved Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 3.9
    • Component/s: Core
    • Labels:
    • Number of attachments :
      0

      Description

      The idea is to map the ExamplesTable rows to match a business object.

      So, instead of having a method like this:

      @Given("some $table")
      public void givenAnExamplesTable(ExamplesTable table)

      { ... }

      We'd have something like this:

      @Given("some $table")
      public void givenACustomBusinessObject(MyCustomObject myObject){ ... }

      Or a List of business objects:
      @Given("some $table")
      public void givenACustomBusinessObject(List<MyCustomObject> myObjectList)

      { ... }

      This can be done in two ways:

      • Annotation-based mapping:

      @AsParameters
      public class MyCustomObject

      { @Parameter(name="column1") private String col1; @Parameter(name="column2") private Boolean col2; }
      • Named-based mapping, using the name of the field as the parameter name.

        Activity

        Hide
        Mauro Talevi added a comment -

        Interesting! Do you have a POC or spike of this somewhere showing it in action?

        Show
        Mauro Talevi added a comment - Interesting! Do you have a POC or spike of this somewhere showing it in action?
        Hide
        Ghislain Nadeau added a comment -

        Working on it

        Show
        Ghislain Nadeau added a comment - Working on it
        Hide
        Ghislain Nadeau added a comment -

        Here's a POC, only using annotations for now...

        git@github.com:JSlain/jbehave-core.git
        branch: pojo_table

        Here's a test that shows it in action:
        org.jbehave.core.io.stories.pojotable.PojoTableStory

        Not sure about the classes name, seems ugly to me...

        I'll continue the POC to support manual mapping, the second way to map a pojo, without annotations.

        Show
        Ghislain Nadeau added a comment - Here's a POC, only using annotations for now... git@github.com:JSlain/jbehave-core.git branch: pojo_table Here's a test that shows it in action: org.jbehave.core.io.stories.pojotable.PojoTableStory Not sure about the classes name, seems ugly to me... I'll continue the POC to support manual mapping, the second way to map a pojo, without annotations.
        Hide
        Ghislain Nadeau added a comment -

        Added support for manually-defined mapping (for example, enable mapping with third-party objects, which you cannot annotate).
        Also added support for methods receiving a single object instead of a list.

        the example now illustrate the 4 usage:

        PojoTableStory.java
            @Given("the following annotated table: $table")
            public void givenExamplesAnnotatedPojo(List<MyAnnotatedPojo> pojos){}
        
            @Given("the following annotated single-element table: $table")
            public void givenExamplesAnnotatedPojo(MyAnnotatedPojo pojo){}
        
            @Given("the following non-annotated table: $table")
            public void givenExamplesNonAnnotatedPojo(List<MyNonAnnotatedPojo> pojos){}
        
            @Given("the following non-annotated single-element table: $table")
            public void givenExamplesAnnotatedPojo(MyNonAnnotatedPojo pojo){}
        
        Show
        Ghislain Nadeau added a comment - Added support for manually-defined mapping (for example, enable mapping with third-party objects, which you cannot annotate). Also added support for methods receiving a single object instead of a list. the example now illustrate the 4 usage: PojoTableStory.java @Given( "the following annotated table: $table" ) public void givenExamplesAnnotatedPojo(List<MyAnnotatedPojo> pojos){} @Given( "the following annotated single-element table: $table" ) public void givenExamplesAnnotatedPojo(MyAnnotatedPojo pojo){} @Given( "the following non-annotated table: $table" ) public void givenExamplesNonAnnotatedPojo(List<MyNonAnnotatedPojo> pojos){} @Given( "the following non-annotated single-element table: $table" ) public void givenExamplesAnnotatedPojo(MyNonAnnotatedPojo pojo){}
        Hide
        Mauro Talevi added a comment -

        Hi Ghislain,

        many thanks for your contribution. The idea is rather interesting, but at a quick glance the code to implement it seems a bit too elaborate for what it gives us.

        To sum up the idea, what you're looking for is to map a list of Parameters objects (as obtainable from the ExamplesTable) to user-defined classes (annotated or not).

        I'm thinking there may be simpler way to do. Let me mull over it.

        Cheers

        Show
        Mauro Talevi added a comment - Hi Ghislain, many thanks for your contribution. The idea is rather interesting, but at a quick glance the code to implement it seems a bit too elaborate for what it gives us. To sum up the idea, what you're looking for is to map a list of Parameters objects (as obtainable from the ExamplesTable) to user-defined classes (annotated or not). I'm thinking there may be simpler way to do. Let me mull over it. Cheers
        Hide
        Ghislain Nadeau added a comment -

        Well, it's a POC.

        Show
        Ghislain Nadeau added a comment - Well, it's a POC.
        Hide
        Mauro Talevi added a comment -

        Sure, and that's why we can use the POC as a starting point and look at different ways to implement it.

        For example, adding a method to ExamplesTable

        public <T> List<T> getRowsAs(Class<T> type);
        

        may be simpler to convert Parameters to type <T>. Then having an annotation on type <T> to trigger use the converter, as you suggested with your @TableDef.

        Show
        Mauro Talevi added a comment - Sure, and that's why we can use the POC as a starting point and look at different ways to implement it. For example, adding a method to ExamplesTable public <T> List<T> getRowsAs( Class <T> type); may be simpler to convert Parameters to type <T>. Then having an annotation on type <T> to trigger use the converter, as you suggested with your @TableDef.
        Hide
        Ghislain Nadeau added a comment -

        Great idea, and that would prevent from touching multiple classes from the core.
        Also, almost all the sources from the poc may only be moved into ExamplesTable for getting it to work.

        Show
        Ghislain Nadeau added a comment - Great idea, and that would prevent from touching multiple classes from the core. Also, almost all the sources from the poc may only be moved into ExamplesTable for getting it to work.
        Mauro Talevi made changes -
        Field Original Value New Value
        Summary Parse ExamplesTable directly into Pojos Allow mapping of ExamplesTable rows to annotated custom types
        Assignee Mauro Talevi [ maurotalevi ]
        Affects Version/s 3.9 [ 19035 ]
        Description I did it once in a team using JBehave.

        The idea is to avoid manually parsing ExamplesTable using rows *Map<String, String>* to match our own business data.

        Instead of having a glue method like this:

        @Given("some $table")
        public void givenAnExamplesTable(ExamplesTable theScenarioTable){ ... }

        We'ld have something like this:

        @Given("some $table")
        public void givenACustomBusinessObject(MyCustomObject myObject){ ... }



        I'm going to work on this, so here's the question.
        Does something like this already exists?
        Otherwise, may I work directly in the core module, or i should start an external one to achieve such a thing?
        I may have to modify ExamplesTableFactory, as stated in JBEHAVE-417


        This can be done in several way...
        Two of my favorites (i'm using fake classes/annotations for the example):

        * Annotations:

        @ExamplesTable
        public class MyCustomObject{
            @ExamplesColumn(mappingName="column1")
            private String col1;

            @ExamplesColumn(mappingName="column2")
            private Boolean col2;
        }

        * Manual mapping

        PojoParameterConverter converter = new PojoParameterConverter<MyCustomObject>(MyCustomObject.class);

        converter.addMapping("column1", "col1");
        converter.addMapping("column2", "col2");
        The idea is to map the ExamplesTable rows to match a business object.

        So, instead of having a method like this:

        @Given("some $table")
        public void givenAnExamplesTable(ExamplesTable table){ ... }

        We'd have something like this:

        @Given("some $table")
        public void givenACustomBusinessObject(MyCustomObject myObject){ ... }

        Or a List of business objects:
        @Given("some $table")
        public void givenACustomBusinessObject(List<MyCustomObject> myObjectList){ ... }

        This can be done in two ways:

        * Annotation-based mapping:

        @AsParameters
        public class MyCustomObject{
            @Parameter(name="column1")
            private String col1;

            @Parameter(name="column2")
            private Boolean col2;
        }

        * Named-based mapping, using the name of the field as the parameter name.
        Hide
        Mauro Talevi added a comment -

        Hi Ghislain,

        I've push an initial implementation. Have a look at latest snapshot. The story you proposed is verified, see examples_table_parameters.story, albeit with a rather slimmer implementation (only add the annotations @AsParameters and @Parameter as new classes).

        Your feedback would be most welcome. Feel free to propose enhancements.

        Cheers

        Show
        Mauro Talevi added a comment - Hi Ghislain, I've push an initial implementation. Have a look at latest snapshot. The story you proposed is verified, see examples_table_parameters.story, albeit with a rather slimmer implementation (only add the annotations @AsParameters and @Parameter as new classes). Your feedback would be most welcome. Feel free to propose enhancements. Cheers
        Hide
        Ghislain Nadeau added a comment - - edited

        Hi Mauro,

        this is actually pretty nice and easy to use!
        I still think it could be of some use to be able to map a third party object by defining a mapping between the third-party fields against the column names.
        Something like:

        ThirdPartyClass.java
        public class ThirdPartyClass{
            private Integer aInteger;
        }
        
        ThirdPartyClass.java
        ParameterMapping map = new ParameterMapping();
        map.addMapping("scenarioColumn1", "aInteger");
        
        examplesTable.getRowsAs(ThirdPartyClass.class, map);
        

        Btw, thanks for reformulating my messages, you might have noticed english is not my native language.

        Show
        Ghislain Nadeau added a comment - - edited Hi Mauro, this is actually pretty nice and easy to use! I still think it could be of some use to be able to map a third party object by defining a mapping between the third-party fields against the column names. Something like: ThirdPartyClass.java public class ThirdPartyClass{ private Integer aInteger; } ThirdPartyClass.java ParameterMapping map = new ParameterMapping(); map.addMapping( "scenarioColumn1" , "aInteger" ); examplesTable.getRowsAs(ThirdPartyClass.class, map); Btw, thanks for reformulating my messages, you might have noticed english is not my native language.
        Hide
        Mauro Talevi added a comment -

        What's the advantage of doing this over the annotations?

        public class ThirdPartyClass{
            @Parameter(name = "scenarioColumn1")
            private Integer aInteger;
        }
        

        If you're accessing the ExampleTable directly then you can use the Parameters interface

        Parameters parameters = examplesTable.getRowsAsParameters().get(0);
        parameters.valueAs("scenarioColumn1", Integer.class);
        
        Show
        Mauro Talevi added a comment - What's the advantage of doing this over the annotations? public class ThirdPartyClass{ @Parameter(name = "scenarioColumn1" ) private Integer aInteger; } If you're accessing the ExampleTable directly then you can use the Parameters interface Parameters parameters = examplesTable.getRowsAsParameters().get(0); parameters.valueAs( "scenarioColumn1" , Integer .class);
        Hide
        Ghislain Nadeau added a comment -

        Well, when i talk about third-party classes, i mean classes from external libraries you're using, but cannot/won't change the sources.
        It would also apply to business objects you'll be using in production, which you don't want to add the smell of test-related annotations.

        You're right about the fact that we can manually use the Parameters class to build another object, i just thought it would be more "from the table to the object" that way.
        In fact, it also lead to another subject i'm going to bring soon, which would also be advantaged by the use of a separated configurable mapping object.

        Show
        Ghislain Nadeau added a comment - Well, when i talk about third-party classes, i mean classes from external libraries you're using, but cannot/won't change the sources. It would also apply to business objects you'll be using in production, which you don't want to add the smell of test-related annotations. You're right about the fact that we can manually use the Parameters class to build another object, i just thought it would be more "from the table to the object" that way. In fact, it also lead to another subject i'm going to bring soon, which would also be advantaged by the use of a separated configurable mapping object.
        Hide
        Mauro Talevi added a comment -

        Right, this usecase could indeed be useful. I've added the ability to specify an optional map with the field name mapping. Check latest snapshot.

        Thanks for your insight and suggestions!

        Show
        Mauro Talevi added a comment - Right, this usecase could indeed be useful. I've added the ability to specify an optional map with the field name mapping. Check latest snapshot. Thanks for your insight and suggestions!
        Hide
        Ghislain Nadeau added a comment -

        Nice!
        Thanks for the development.
        I guess you can close the issue.

        Show
        Ghislain Nadeau added a comment - Nice! Thanks for the development. I guess you can close the issue.
        Mauro Talevi made changes -
        Status Open [ 1 ] Resolved [ 5 ]
        Resolution Fixed [ 1 ]

          People

          • Assignee:
            Mauro Talevi
            Reporter:
            Ghislain Nadeau
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: