Skip to content

Commit

Permalink
docs: add documentation for column transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-granados committed Feb 14, 2025
1 parent 1e3c8f9 commit 1fca7e1
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 185 deletions.
1 change: 1 addition & 0 deletions user_guide/context.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ context classes are.

context/hooks
context/definitions
context/transformations


Context Class Requirements
Expand Down
186 changes: 1 addition & 185 deletions user_guide/context/definitions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ a different argument:
testing.

Casting arguments to specific types can be accomplished using
`step argument transformations`_.
:doc:`step argument transformations</user_guide/context/transformations>`

.. note::

Expand Down Expand Up @@ -505,190 +505,6 @@ feature context:
Executing Behat with this feature context will result in a ``Redundant``
exception being thrown.

Step Argument Transformations
-----------------------------

Step argument transformations allow you to abstract common operations performed
on step definition arguments into reusable methods. In addition, these methods
can be used to transform a normal string argument that was going to be used
as an argument to a step definition method, into a more specific data type
or an object.

Each transformation method must return a new value. This value then replaces
the original string value that was going to be used as an argument to a step
definition method.

Transformation methods are defined using the same attribute style as step
definition methods, but instead use the ``Transform`` attribute with
a matching pattern as argument.

As a basic example, you can automatically cast all numeric arguments to
integers with the following context class code:

.. code-block:: php
// features/bootstrap/FeatureContext.php
use Behat\Behat\Context\Context;
use Behat\Step\Then;
use Behat\Transformation\Transform;
class FeatureContext implements Context
{
#[Transform('/^(\d+)$/')]
public function castStringToNumber($string)
{
return intval($string);
}
#[Then('a user :name, should have :count followers')]
public function assertUserHasFollowers($name, $count)
{
if ('integer' !== gettype($count)) {
throw new Exception('Integer expected');
}
}
}
.. note::

In the same way as with step definitions, you can use both simple patterns and
regular expressions.

Let's go a step further and create a transformation method that takes an
incoming string argument and returns a specific object. In the following
example, our transformation method will be passed a username, and the method
will create and return a new ``User`` object:

.. code-block:: php
// features/bootstrap/FeatureContext.php
use Behat\Behat\Context\Context;
use Behat\Step\Then;
use Behat\Transformation\Transform;
class FeatureContext implements Context
{
#[Transform(':user')]
public function castUsernameToUser($user)
{
return new User($user);
}
#[Then('a :user, should have :count followers')]
public function assertUserHasFollowers(User $user, $count)
{
if ('integer' !== gettype($count)) {
throw new Exception('Integer expected');
}
}
}
Table Transformation
~~~~~~~~~~~~~~~~~~~~

Let's pretend we have written the following feature:

.. code-block:: gherkin
# features/table.feature
Feature: Users
Scenario: Creating Users
Given the following users:
| name | followers |
| everzet | 147 |
| avalanche123 | 142 |
| kriswallsmith | 274 |
| fabpot | 962 |
And our ``FeatureContext`` class looks like this:

.. code-block:: php
// features/bootstrap/FeatureContext.php
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
use Behat\Step\Given;
class FeatureContext implements Context
{
#[Given('the following users:')]
public function pushUsers(TableNode $usersTable)
{
$users = array();
foreach ($usersTable as $userHash) {
$user = new User();
$user->setUsername($userHash['name']);
$user->setFollowersCount($userHash['followers']);
$users[] = $user;
}
// do something with $users
}
}
A table like this may be needed in a step testing the creation of the
``User`` objects themselves, and later used again to validate other parts of
our codebase that depend on multiple ``User`` objects that already exist.
In both cases, our transformation method can take our table of usernames and
follower counts and build dummy ``User`` objects. By using a transformation
method we have eliminated the need to duplicate the code that creates our
``User`` objects, and can instead rely on the transformation method each time
this functionality is needed.

Transformations can also be used with tables. A table transformation is matched
via a comma-delimited list of the column headers prefixed with ``table:``:

.. code-block:: php
// features/bootstrap/FeatureContext.php
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
use Behat\Step\Given;
use Behat\Step\Then;
use Behat\Transformation\Transform;
class FeatureContext implements Context
{
#[Transform('table:name,followers')]
public function castUsersTable(TableNode $usersTable)
{
$users = array();
foreach ($usersTable->getHash() as $userHash) {
$user = new User();
$user->setUsername($userHash['name']);
$user->setFollowersCount($userHash['followers']);
$users[] = $user;
}
return $users;
}
#[Given('the following users:')]
public function pushUsers(array $users)
{
// do something with $users
}
#[Then('I expect the following users:')]
public function assertUsers(array $users)
{
// do something with $users
}
}
.. note::

Transformations are powerful and it is important to take care how you
implement them. A mistake can often introduce strange and unexpected
behavior. Also, they are inherently hard to debug because of their
highly dynamic nature.


.. tip::
Behat provides a :ref:`command line
option<user-guide--command-line-tool--informative-output--print-definitions>`
Expand Down
Loading

0 comments on commit 1fca7e1

Please sign in to comment.