Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add documentation for column transformers #189

Merged
merged 1 commit into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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