The SpecFlow Cook Book

LinkingTableRows

Linking table rows

Relate an entity defined in one table, with a different entity defined in another

.

The Problem

While you navigate your way through the implementation of automated tests using specflow it is inevitable that you will encounter a situation that involves the use of tables either directly within the steps of your scenarios or in background steps designed to setup the data required by your tests. I guarantee that you will also wind up in a place where each row in your table relates, in some way, to an entity or other object that exists within the application you are testing and at that point you are on a path which will lead you straight into the problem of how you might be able to relate an entity defined in one of these tables, with a different entity defined in another.

What I am talking about here is how we can use the text within our feature files to define an object graph. A series of related entities bound together by primary keys which are unknown to us until runtime.

The Ingredients

  • A method for relating rows in two different tables using only the text appearing in those tables
  • A generic solution minimizing the need for step code to be written for every type of entitiy
  • Use of ScenarioContext to share background data between steps
  • Extensions for ScenarioContext and the Specflow Table

The Solution

Its Christmas eve as I sit here writing this recipe, so the following example is suitably festive. Consider a system where we automate the sending of emails to Santa Claus given the contact details of children and details of the presents they would like to receive. One of the test scenarios for such a system might look like this:



Scenario: Letters to Santa appear in the emailers outbox

Given the following "Children" exist
| First Name | Last Name | Age |
| Noah       | Smith     | 6   |
| Oliver     | Thompson  | 3   |

And the following "Gifts" exist
| Child           | Type     | Colour |
| Noah Smith      | Lego Set |        |
| Oliver Thompson | Robot    | Red    |
| Oliver Thompson | Bike     | Blue   |

When the letters to Santa are generated

Then the following letters appear in the emailers outbox
| From            | Age | Content                                  |
| Noah Smith      | 6   | I would like Lego                        |
| Oliver Thompson | 3   | I would like a red robot and a blue bike |


Here we have table A (the “Children”) containing the names of two children who want to write to Santa, and in table B (the “Gifts”) we have some basic details of the presents they would like to ask for. Here it is clear to see that we have details of 1 gift for Noah, and 2 gifts for Oliver, and that the entries in the gift table are related to the children using their full names. The step code may therefore look something like this:



[Given("(?i)the following \"children\" exist")]
public void CreateChildren(Table data)
{
  foreach (var row in data.Rows)
  {
    // Create a new child entity
    var child = CreateChild(row);

    // Add the child to scenario context for retrieval in other steps
    ScenarioContext.Current.Add(string.Format("Child[{0}]", child.Id), child);
  }
}

[Given("(?i)the following \"gifts\" exist")]
public void CreateGifts(Table data)
{
  foreach (var row in data.Rows)
  {
    // Retrieve the child from scenario context using their full name
    var fullName = row["Child"];
    var children = ScenarioContext.Current.OfType();
    var child = children.Single(c => (c.FirstName + " " + c.LastName) == fullName));

    // Create a new gift entity
    var gift = CreateGift(row, child);

    // Add the gift to scenario context for retrieval in other steps
    ScenarioContext.Current.Add(string.Format("Gift[{0}]", gift.Id), gift);
  }
}


Pros

  • The content of the feature file is very easy to understand and could easily be authored and understood by non technical people.
  • The step implementation is simple, and although there is definite room for improvement the step code is easy to understand and maintain


Cons

  • The step code is very custom for the entities we are dealing with. In our system we currently only have two entity types (“child” and “gift”) but in a real world system it is likely that there will be 10′s and probably 100′s of entities. Do we really want to be writing step code every time the developers deliver a new type of entity?
  • The solution is lacking convention. Here we are using a combination of first and last name to refer to the row from the child table, however what if we were dealing with insurance policies. Would we use policy number? What about bank accounts? Would account number suffice or would we combine this with the accounts sort code. Clearly, to stand any chance of implementing a common step to handle any entity and its relationship to others we need to invent a convention, something that enables us to link table entries together within the feature file using a language that is non technical and easy enough to communicate to our feature authors

Before we start looking at some options lets take a closer look at the entities we are trying to create:



public class Child
{
  /// 
  /// Gets or sets the childs primary key
  /// 
  public Guid Id { get; set; }
    
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public int Age { get; set; }
}

public class Gift
{
  /// 
  /// Gets or sets the gifts primary key.
  /// 
  public Guid Id { get; set; }

  /// 
  /// Gets or sets the foreign key to the child.
  /// 
  public Guid Child { get; set; }

  public string Type { get; set; }
  public string Colour { get; set; }
}


Again, nothing especially complicated there. The only thing to really note is that our objects both have a primary key which is a Guid, and that the gift is linked to a child using the childs primary key.

Something else that is worth noting is that the primary key field is called “Id” in both cases. This is part of the objects technical implementation and as such is something that should really be excluded, or at best hidden from view within out feature because it is not something we really want our audience to know about.

We need to define a few things:

  • Which “Gift” property are we going set and where will its value come from?
  • Which table contains the “Child” I want associate with the “Gifts”?
  • Given that we known where to look for the correct “Child”, how will be find the correct one?

Consider the following:



Scenario: Letters to Santa appear in the emailers outbox

Given the following "Children" exist
| First Name | Last Name | Age |
| Noah       | Smith     | 6   |
| Oliver     | Thompson  | 3   |

And the following "Gifts" exist
| Child using id from Children | Type     | Colour |
| Last Name is Smith           | Lego Set |        |
| Last Name is Thompson        | Robot    | Red    |
| Last Name is Thompson        | Bike     | Blue   |

When the letters to Santa are generated

Then the following letters appear in the emailers outbox
| From            | Age | Content                                  |
| Noah Smith      | 6   | I would like Lego                        |
| Oliver Thompson | 3   | I would like a red robot and a blue bike |


Here I am using the heading of the “Gifts” Specflow table to solve the first two points, namely what property to set (gift.child), where to find the child (children table), and which child property to read (id).

Child using Id from Chilren

  • Set the “Child” property of the gift to….
  • The “Id” of the correct row from….
  • The “Children” table

Then on each gift row we have something that looks like this:

Last Name is Smith

  • When looking in the “Children” table (defined by the above)
  • Find the row which has a value of “Smith” in the….
  • “Last Name” column

    Pros

    • Very easy to read and understand with a little guidance.
    • Completely flexible and will satisfy the need to link any row in one table with any row in another based on the value in one of its columns
    • Step implementation for any entity type can be achieved with a single method

    Cons

    • The step implementation will inevitably be quite complex
    • We have “leaked” knowledge of “Id” into the scenario.

    Primary keys, Id’s and object types are just technical glue, and as such should be excluded, or at least hidden from our feature audience because they are things that they should not really have to know about.

    You could argue that in your particular system that 90% of the time you are going to want to resolve the “Id” of the foreign entity, and therefore it would make more sense to assume this when “using x” is not present in the table header. This is perfectly sensible and would then result in something which is even simpler:

    
    
    And the following "Gifts" exist
    | Child from Children     | Type     | Colour |
    | Last Name is Smith      | Lego Set |        |
    | Last Name is Thompson   | Robot    | Red    |
    | Last Name is Thompson   | Bike     | Blue   |
    
    
    

    This now means that we have a fairly generic way of linking table rows together, so we can go crazy and start adding tables for any entity type that takes our fancy! For instance, suppose we want to wrap the gifts:

    
    
    Scenario: Letters to Santa appear in the emailers outbox
    
    Given the following "Children" exist
    | First Name | Last Name | Age |
    | Noah       | Smith     | 6   |
    | Oliver     | Thompson  | 3   |
    
    And the following "Gifts" exist
    | Child from Children    | Type     | Colour |
    | Last Name is Smith     | Lego Set |        |
    | Last Name is Thompson  | Robot    | Red    |
    | Last Name is Thompson  | Bike     | Blue   |
    
    And the following "Wrapping"
    | Gift from Gifts  | Type  | Size   | Pattern or Design        | 
    | Type is Lego Set | Box   | Small  | None                     |
    | Type is Robot    | Tube  | Medium | Nuts and Bolts           | 
    | Type is Bike     | Paper | Large  | Blue, with white stripes | 
    
    
    

    Example

    LinkTableRows

    Above is a link to a solution which demonstrates the use of this approach.

    Points of interest

    We have used a step implementation for each entity type

    It would be possible to have a single step binding which uses reflection to create your desired entity, however for the purposes of this demonstration and in the interests of keeping things simple we have created a very simple step binding for each of the entity types

    “But there’s no code in the entity steps!??!!”

    This is because the step method expects IEnumerable or IEnumerable as its parameter rather than Table (this approach is dealt with in the chapter which talks about StepArgumentTransformations). This means that Specflow will look for a method which returns these types, which takes as its parameter a Table, and which has been decorated with the “StepParameterTransformation” attribute. You will find these transformation steps in StepArgumentTransoformations.cs

    The way you instantiate your entities is left you
    Most of the “cleaverness” in dealing with the specflow tables is hidden behind the “Save” extension method, however this code is not capable

    of directly interacting with the objects in your system, because it knows nothing about your entity types. It instead maintains its own object meta data to enable it maintain the relationships between each row in the table based solely on the values in the tables columns, and therefore relies on you to provide methods which a) create the object instance from the data it provides back to you, and b) provide a mechanism for identifying the primary id for those object. In our implementation we rely on the “CreateInstance” extension method provided by the TechTalk.SpecFlow.Assist namespace.

    “What if we want to use a different convention?”

    Thats fine. You will see that there is a regular expression defined in the “ResolveDependencies” method within the SpecflowExtension.cs code file. This expression is used to extract the key components for the table containing the entities, the field we want to set, the field we want to read, the field we want to compare, and the value we want to compare it to. All you have to do is provide an alternative implementation for obtaining these items.

close
LinkingTableRows

Linking table rows

Leave a Reply

Simon Parsons

Simon Parsons

SIPSoftware was established in 2005 and provides IT services to several major Blue-Chip companies. We specialize in Agile software development and help our customers achieve excellence through the use of Scrum, continuous deployment and test automation I specialize in software development and test automation using .Net and have a particular interest in BDD using Specflow and Selenium WebDriver.

Contact


Search