Behat TableNodes – the missing manual

We’ve been using Behat for Behaviour-Driven Development for some time now, and it’s been a fantastic tool.  If you don’t know it, it uses Gherkin to allow you to create tests that describe user behaviour in (almost) natural language.

If you have come across Behat before, you probably know that one particularly useful feature is tabular data.  This allows you to write scenarios which include lines like the following:

Scenario:
  Given the following people exist:
    | name  | email           | phone |
    | Aslak | aslak@email.com | 123   |
    | Joe   | joe@email.com   | 234   |

An overview of how to use tabular data is given in the Behat docs.  As those show, the table data is available to your supporting PHP code as a TableNode object.

The TableNode class is described in more detail in the Gherkin API.  However, the Gherkin API for TableNode is sparse, giving each method a single sentence of description.  I recently spent some time working with the gory details of TableNodes, so this blog post is the manual I wish I’d had!  I was working on a method to add a column of data to a TableNode, so I’ll show that as well.

I’m not going to document every method of the TableNode, just those to do with extracting and adding data.  In the examples below, I’m assuming I’ve got the table of data shown above.

First up – hash methods

The most important thing to bear in mind is the difference between TableNode’s getXXX and getXXXHash methods.  The standard getXXX methods treat every row/col as the same, including the header, giving everything numeric keys.  The getXXXHash methods use the first row/col as the header and thus as the array keys too.

addRow(string|array row, null|integer line)

This one is pretty self-explanatory, but a word of warning up-front:  Remember that TableNodes are designed to hold representations of table strings.  Don’t make the mistake I did of using this method to add an array which contains objects!  You won’t get an error when adding, but your code will fail silently when you try and use the resulting object…

getRows()

Returns an array of arrays, one per row.  Each row array contains one element per “cell” in that row.  All indexes are numeric.

var_dump shows that this function returns the following for the sample table at the start of this blog:

array(3) {
  [0]=>
  array(3) {
    [0]=>
    string(4) "name"
    [1]=>
    string(5) "email"
    [2]=>
    string(5) "phone"
  }
  [1]=>
  array(3) {
    [0]=>
    string(5) "Aslak"
    [1]=>
    string(15) "aslak@email.com"
    [2]=>
    string(3) "123"
  }
  [2]=>
  array(3) {
    [0]=>
    string(3) "Joe"
    [1]=>
    string(13) "joe@email.com"
    [2]=>
    string(3) "234"
  }
}

Use getRow(n) to extract a single row instead.

getRowAsString(integer rowNum)

Returns the row in the string format used to create it above.  E.g.

| Joe   | joe@email.com   | 234   |

 

getHash()

Assumes the the first row of your data is a header and returns an array of arrays, one per “table body” row.  These row arrays have keys based on the header and values from the row in question.

For example:

array(2) {
  [0]=>
  array(3) {
    ["name"]=>
    string(5) "Aslak"
    ["email"]=>
    string(15) "aslak@email.com"
    ["phone"]=>
    string(3) "123"
  }
  [1]=>
  array(3) {
    ["name"]=>
    string(3) "Joe"
    ["email"]=>
    string(13) "joe@email.com"
    ["phone"]=>
    string(3) "234"
  }
}

getRowsHash()

Like getHash, but assumes that your data has a heading column rather than a heading row.  Since that’s not true of the data above, the output is a bit odd:

array(3) {
  ["name"]=>
  string(5) "email"
  ["Aslak"]=>
  string(15) "aslak@email.com"
  ["Joe"]=>
  string(13) "joe@email.com"
}

You’ll notice also that it only considers the first column apart from the header column.  Not sure why there’s this inconsistency between getRowsHash and getHash!

The methods in the rest of this document are a relatively recent addition to Gherkin, so you won’t be able to use them if you’re running an older version of behat.

getNumeratedRows()

This is identical to getRows, with one exception:  Rather than the array keys being 0-indexed, they instead correspond to the line number of the feature’s source-code on which that table row was written.  Sample output from the scenario in my feature:

array(3) {
  [7]=>
  array(3) {
    [0]=>
    string(4) "name"
    [1]=>
    string(5) "email"
    [2]=>
    string(5) "phone"
  }
  [8]=>
  array(3) {
    [0]=>
    string(5) "Aslak"
    [1]=>
    string(15) "aslak@email.com"
    [2]=>
    string(3) "123"
  }
  [9]=>
  array(3) {
    [0]=>
    string(3) "Joe"
    [1]=>
    string(13) "joe@email.com"
    [2]=>
    string(3) "234"
  }
}

Notice the keys of 7, 8 and 9.  Blank lines are included in the count, so it matches what your editor would tell you about line numbers.

getRowLines()

Similar to getNumeratedRows, but only returns the line numbers and not the data.  For example:

array(3) {
  [0]=>
  int(6)
  [1]=>
  int(7)
  [2]=>
  int(8)
}

getLine()

Returns a number corresponding to the line number of the feature’s source code on which the TableNode’s first line is written.  Continuing the example above, this would return 6.

Adding a column

What about the reason I started digging into TableNodes in the first place?  It was all because I wanted to write some code to dynamically add a column.

Well, here’s the source code of what I went for in the end.  Hopefully reading it will help you better understand some of the methods explained earlier:

   public function addTableNodeColumn(TableNode $table, $colName, $colValue)
    {
        $extendedTable = new TableNode();
        $rowNum = 0;

        foreach ($table->getRows() as $row) {
            if ($rowNum == 0) { // header row
                $row[] = $colName;
            }
            else { // body
                $row[] = strval($colValue);
            }
            $extendedTable->addRow($row);
            $rowNum++;
        }

        return $extendedTable;
    }

Happy BDDing!

2 Comments

  1. Posted 7 July 2013 at 2:49 pm | Permalink

    Hey there! Thanks very much for this post. I was writing some Behat tests and realised that there was no comprehensive docs out there for the table node.

  2. Posted 26 November 2014 at 12:37 pm | Permalink

    Thank you very much.

Post a Comment

Your email address is never published nor shared. Required fields are marked *

Ready to talk?

Whether you want to create a new digital product or make an existing one even better, we'd love to talk it through.

Get in touch