Skip to content

Multiline Step Arguments

jbescoyez edited this page Jan 25, 2012 · 45 revisions

The regular expression matching in Step Definitions lets you capture small strings from your
steps and receive them in your step definitions. However, there are times when you want to pass
a richer data structure from a step to a step definition.

This is what multiline step arguments are for. They are written on the lines right underneath a step,
and will be yielded as the last argument to the matching step definition’s block.

Multiline step arguments come in two flavours – tables or strings.

Data Tables

Tables as arguments to steps are handy for specifying a larger data set – usually as input to a Given or as expected output from a Then. (This is not to be confused with tables in Scenario outlines – syntactically identical, but with a different purpose). Example:

Given the following people exist:
  | name  | email           | phone |
  | Aslak | [email protected] | 123   |
  | Joe   | [email protected]   | 234   |
  | Bryan | [email protected] | 456   |

-and a matching step definition:

Given /the following people exist:/ do |people_table|
  people_table.hashes.each do |hash|
    # The first time the +hash+ will contain:
    #   {'name' => 'Aslak', 'email' => '[email protected]', 'phone' => '123'} 
    # The second time:
    #   {'name' => 'Joe', 'email' => '[email protected]', 'phone' => '234'}
    # etc.
  end
end

A plain text table gets stored in a Cucumber::Ast::Table object, which provides several methods to access the
rows, columns and cells in the table. In the example above we’re using the Cucumber::Ast::Table#hashes method to iterate over all of the rows.

Diffing tables

One very powerful feature in Cucumber is comparison of tables. You can compare a table argument to another table that you provide within your step definition. This is something you would typically do in a Then step, and the other table would typically be constructed programmatically from your application’s data.

Beware that the diffing algorithm expects your data to be column-oriented, and that the first row of both tables represents column names. If your tables don’t have some similarity in the first row you will not get very useful results. The column names must be unique for each column – and they must match. However, the column order is insignificant.

The diffing is done with the Cucumber::Ast::Table#diff!(table) method. Here is an example:

Then I should see the following cukes:
  | Latin           | English      |
  | Cucumis sativus | Cucumber     |
  | Cucumis anguria | Burr Gherkin |

And a Step Definition that uses diff!:

Then /^I should see the following cukes:$/ do |expected_cukes_table|
  actual_table = [
    ['Latin', 'English'],
    ['Cucumis sativus', 'Concombre'],
    ['Cucumis anguria', 'Burr Gherkin']
  ] # In practice you'd pull this out of a web page, database or some other data store.

  expected_cukes_table.diff!(actual_table)
end

Cucumber will print the difference:

Latin English
Cucumis sativus Cucumber
Cucumis sativus Concombre
Cucumis anguria Burr Gherkin

(Due to limitations of this wiki, this will look slightly different in a console).

Missing rows and columns are printed in grey (same colour as comments), and surplus rows and columns are printed in yellow (same colour as undefined steps). When the two tables differ, the step will fail. You can modify this behaviour by passing an options Hash to the diff! method. See RDoc for details.

The table argument to the diff! method can be one of the following:

  • Another CucumberAst::Table
  • An Array containing Arrays with Strings
  • An Array containing Hashes with String => String (similar to the structure returned by CucumberAst::Table#hashes).

In most cases you have to construct that table yourself programmatically, but for convenience, Cucumber ships with a handy method for Webrat users that will turn a HTML table into an Array of Array. Example:

expected_cukes_table.diff!(tableish('table#cuke_table tr', 'td,th'))

-Or if you are on Cucumber 0.4.4 or older:

expected_cukes_table.diff!(table_at('#cuke_table').to_a)

Stripping HTML

If your HTML Table contains e.g. links and you want to create a table structure containing the link text, you can convert the column values. Example:

actual_table = table(tableish('table#cuke_table tr', 'td,th'))
actual_table.map_column!('Artist') { |text| text.strip.match(/>(.*)</)[1] }
expected_cukes_table.diff!(actual_table)

For a more complex example, look at this commit which uses a Proc to extract each column from the HTML. (This is a spec for the internal _tableish method – in your own code you want to use tableish instead and skip the 1st html argument).

Note that tableish is now deprecated. The example hereover could be rewrittent like:

rows = find("table#selector").all('tr')
table = rows.map { |r| r.all('th,td').map { |c| c.text.strip } }
expected_table.diff!(table)

As specified on Dennis Reimann’s blog .

Transforming tables with Transform

If you want to transform an entire table to another data structure, you can use Step Argument Transforms.

ActiveRecord

You can also easily turn Rails ActiveRecord objects into a table:

expected_cukes_table.diff!(Cuke.find(:all).map(&:attributes))

(This is not a recommended practice – your Then steps should compare against what users see, and they can’t see your database).

If you are not using Webrat or ActiveRecord, you can build up table structures yourself. Use your imagination!

Multiline Strings

Multiline Strings are handy for specifying a larger piece of text. This is done using the PyString syntax. (The inspiration for PyString comes from Python where """ is used to delineate docstrings.) The text should be offset by delimiters consisting of three double-quote marks on lines of their own:

Given a blog post named "Random" with Markdown body
  """
  Some Title, Eh?
  ==============
  Here is the first paragraph of my blog post. Lorem ipsum dolor sit amet,
  consectetur adipiscing elit.
  """

In your step definition, there’s no need to find this text and match it in your Regexp. It will automatically be yielded as the last parameter in the step definition. For example:

Given /^a blog post named "([^\"]*)" with Markdown body$/ do |title, markdown|
  Post.create!(:title => title, :body => markdown)
end

Indentation of the opening """ is unimportant, although common practice is two spaces in from the enclosing step. The indentation inside the triple quotes, however, is significant. Each line of the string passed to the step definition’s block will be de-indented according to the opening """. Indentation beyond the column of the opening """ will therefore be preserved.

Cucumber stores multiline strings internally as a Cucumber::Ast::PyString object. However,
this gets converted to a regular String before it’s passed to the step definition.

Diffing multiline strings

If you’re using RSpec Expectations, you can compare strings simply with

string_from_step.should == actual_string

If they are different, the step will fail with an error displaying the difference as a unified diff.

Substitution in Scenario Outlines

If you use a multiline argument in steps in Scenario Outlines, any < >
delimited tokens will be substituted with values from the example tables. For example:

Scenario Outline: Email confirmation
  Given I have a user account with my name "Jojo Binks"
  When an Admin grants me <Role> rights
  Then I should receive an email with the body:
    """
    Dear Jojo Binks,
    You have been granted <Role> rights.  You are <details>. Please be responsible.
    -The Admins
    """
  Examples:
    |  Role     | details                                         |
    |  Manager  | now able to manage your employee accounts       |
    |  Admin    | able to manage any user account on the system   |

Or with tables:

Scenario Outline: Multiplying with 7
  Given I have the numbers
    | q1   | q2 | product |
    | <x>  |  7 | <y>     |
  Examples:
    |  x |  y |
    |  2 | 14 |
    | 10 | 70 |

Using regular arguments and multiline arguments

It’s possible to pass regular arguments in addition to multiline arguments. This is illustrated
for strings above in the blog example. Here is an example using tables:

# ruby
Given /^an expense report for (.*) with the following posts:$/ do |date, posts_table|
  # The posts_table variable is an instance of Cucumber::Ast::Table
end

This can easily be called from a plain text step like this:

# feature
Given an expense report for Jan 2009 with the following posts:
  | account | description | amount |
  | INT-100 | Taxi        |    114 |
  | CUC-101 | Peeler      |     22 |

Also see Calling Steps from Step Definitions for a related example.

Clone this wiki locally