Behat
Behat.phar Mink.phar GitHub

Defining Reusable Actions - Step Definitions

Gherkin language provides a way to describe your application behavior in business readable language. But how do you test that the described behavior is actually implemented? Or that the application satisfies our business expectations described in feature scenarios? Behat provides a way to map 1-to-1 your scenario steps (actions) to actual PHP code called step definitions:

/**
 * @When /^I do something with "([^"]*)"$/
 */
public function iDoSomethingWith($argument)
{
    // do something with $argument
}

Definitions Home - FeatureContext Class

Step definitions are just normal PHP methods. They are instance methods in a special class called Feature Context. This class can be easily created by running behat with the --init option from your project’s directory:

$ behat --init

After you run this command, Behat will set up a features directory inside your project:

../_images/--init.png

The newly created features/bootstrap/FeatureContext.php will have an initial context class to get you started:

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\ClosuredContextInterface,
    Behat\Behat\Context\TranslatedContextInterface,
    Behat\Behat\Context\BehatContext,
    Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;

class FeatureContext extends BehatContext
{
    // Your definitions will live here
}

All step definitions and hooks necessary for behavior testing your project will be represented as methods inside this class.

Creating Your First Step Definition

The main goal for step definitions is to be executed when its matching step is run in Behat. But just because a method exists within FeatureContext doesn’t mean Behat can find it. Behat needs a way to check that a concrete class method is suitable for a concrete step in a scenario. Behat matches FeatureContext methods to step definitions using regular expression matching.

When Behat runs, it compares special lines of Gherkin from each scenario to the regular expressions bound to each method in FeatureContext. If the line of Gherkin satisfies a bound regular expression, its corresponding step definition is executed. It’s that simple!

Behat uses phpDoc annotations to bind regular expressions to FeatureContext methods:

/**
 * @When /^I do something with "([^"]*)"$/
 */
public function someMethod($methodArgument) {}

Let’s take a closer look at this code:

  1. @When is a definition keyword. There are 3 supported keywords in annotations: @Given/@When/@Then. These three definition keywords are actually equivalent, but all three are available so that your step definition remains readable.
  2. The text after the keyword is the regular expression (e.g. /^I am in a directory "([^"]*)"$/).
  3. All search patterns in the regular expression (e.g. ([^"]*)) will become method arguments ($methodArgument).

Note

Notice the comment block starts with /**, and not the usual /*. This is important for Behat to be able to parse such comments as annotations!

As you probably noticed, this regular expression is quite general and its corresponding method will be called for steps that contain ... I do something with "...", including:

Given I do something with "string1"
 When I do something with "some other string"
 Then I do something with "smile :-)"

The only real difference between those steps in the eyes of Behat is the text inside double quotes. This text will be passed to its step’s corresponding method as an argument value. In the example above, FeatureContext::someMethod() will be called three times, each time with a different argument:

  1. ->someMethod( $methodArgument = 'string1' );.
  2. ->someMethod( $methodArgument = 'some other string' );.
  3. ->someMethod( $methodArgument = 'smile :-)' );.

Note

Regular expression can’t automatically determine the datatype of its matches. So all method arguments coming from step definitions are passed as strings. Even if your regular expression matches “500”, which could be considered an integer, “500” will be passed as a string argument to the step definition’s method.

This is not a feature or limitation of Behat, but rather the inherent way regular expression matching works. It is your responsibility to cast string arguments to integers, floats or booleans where applicable given the code you are testing.

Casting arguments to specific types can be accomplished using step argument transformations.

Note

Behat does not differentiate between step keywords when matching regular expressions to methods. So a step defined with @When could also be matched to @Given ..., @Then ..., @And ..., @But ..., etc.

Your step definitions can also define multiple arguments to pass to its matching FeatureContext method:

/**
 * @When /^I do something with "([^"]*)" and with (\d+)$/
 */
public function someMethod($stringArgument, $numberArgument) {}

Definition Snippets

You now know how to write step definitions by hand, but writing all these method stubs, annotations and regular expressions by hands is tedious. Behat makes this routine task much easier and fun by generating definition snippets for you! Let’s pretend that you have this feature:

# features/example.feature
Feature:
  Scenario:
    Given some step with "string" argument
    And number step with 23

Run this feature in Behat:

$ behat features/example.feature

Behat will provide auto-generated snippets for your steps:

../_images/definitions-snippets.png

It not only generates the proper definition annotation type (@Given), but also a regular expression with string ("([^"]+)") or number ((\d+)) capturing, method name (someStepWithArgument(), numberStepWith()) and arguments ($argument1), all based just on text of the step. Isn’t that cool?

The only thing left for you to do is to copy that method snippets into your FeatureContext class and provide a useful body for them!

Step Execution Result Types

Now you know how to map actual code to PHP code that will be executed. But how can you tell what exactly “failed” or “passed” when executing a step? And how does Behat actually check that a step executed properly?

For that we have step execution types. Behat differentiates between seven types of step execution results: “Successful Steps”, “Undefined Steps”, “Pending Steps”, “Failed Steps”, “Skipped Steps”, “Ambiguous Steps” and “Redundant Step Definitions”.

Let’s use our previously introduced feature for all the following examples:

# features/example.feature
Feature:
  Scenario:
    Given some step with "string" argument
    And number step with 23

Successful Steps

When Behat finds a matching step definition it will execute it. If the definition method does not throw an Exception, the step is marked as successful (green). What you return from a definition method has no significance on the passing or failing status of the definition itself.

Let’s pretend our context class contains the code below:

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    /** @Given /^some step with "([^"]*)" argument$/ */
    public function someStepWithArgument($argument1)
    {
    }

    /** @Given /^number step with (\d+)$/ */
    public function numberStepWith($argument1)
    {
    }
}

When you run your feature, you’ll see all steps passed and are marked green:

../_images/definitions-successful.png

Note

Passed steps are always marked as green if colors are supported by your console.

Tip

Install php5-posix on Linux, Mac OS or other Unix system to be able to see colorful Behat output.

Undefined Steps

When Behat cannot find a matching definition, the step are marked as undefined, and all subsequent steps in the scenario are skipped.

Let’s pretend we have an empty context class:

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
}

When you run your feature, you’ll get 2 undefined steps that are marked yellow:

../_images/definitions-snippets.png

Note

Undefined steps are always marked as yellow if colors are supported by your console.

Note

All steps following an undefined step are not executed, as the behavior following it is unpredictable. These steps are marked as skipped.

Tip

If you use the --strict option with Behat, undefined steps will cause Behat to exit with 1 code.

Pending Steps

When a definition method throws a Behat\Behat\Exception\PendingException exception, the step is marked as pending, reminding you that you have work to do.

Let’s pretend your FeatureContext looks like this:

<?php

use Behat\Behat\Context\BehatContext,
    Behat\Behat\Exception\PendingException;

class FeatureContext extends BehatContext
{
    /** @Given /^some step with "([^"]*)" argument$/ */
    public function someStepWithArgument($argument1)
    {
        throw new PendingException('Do some string work');
    }

    /** @Given /^number step with (\d+)$/ */
    public function numberStepWith($argument1)
    {
        throw new PendingException('Do some number work');
    }
}

When you run your feature, you’ll get 1 pending step that is marked yellow:

../_images/definitions-pending.png

Note

Pending steps are always marked as yellow if colors are supported by your console, because they are logically similar to undefined steps.

Note

All steps following a pending step are not executed, as the behavior following it is unpredictable. These steps are marked as skipped.

Tip

If you use --strict option with Behat, pending steps will cause Behat to exit with 1 code.

Failed Steps

When a definition method throws a generic Exception (not a PendingException) during execution, the step is marked as failed. Again, what you return from a definition does not affect the passing or failing of the step. Returning null or false will not cause a step definition to fail.

Let’s pretend, that your FeatureContext has following code:

<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    /** @Given /^some step with "([^"]*)" argument$/ */
    public function someStepWithArgument($argument1)
    {
        throw new Exception('some exception');
    }

    /** @Given /^number step with (\d+)$/ */
    public function numberStepWith($argument1)
    {
    }
}

When you run your feature, you’ll get 1 failing step that is marked red:

../_images/definitions-failed.png

Note

Failed steps are always marked as red if colors are supported by your console.

Note

All steps following a failed step are not executed, as the behavior following it is unpredictable. These steps are marked as skipped.

Tip

If Behat finds a failed step during suite execution, it will exit with 1 code.

Tip

Behat does not come with its own assertion library, but you can use any proper assertion tool out there, as long as its failed assertions throw an exceptions. For example, if you’re familiar with PHPUnit, you can use its assertions in Behat:

# features/bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\BehatContext;
use Behat\Gherkin\Node\PyStringNode;

require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';

class FeatureContext extends BehatContext
{
    /**
     * @Then /^I should get:$/
     */
    public function iShouldGet(PyStringNode $string)
    {
        assertEquals($string->getRaw(), $this->output);
    }
}

Tip

You can get exception stack trace with -v option provided to Behat:

$ behat features/example.feature -v

Skipped Steps

Steps that follow undefined, pending or failed steps are never executed, even if there is a matching definition. These steps are marked skipped:

../_images/definitions-pending.png

Note

Skipped steps are always marked as cyan if colors are supported by your console.

Ambiguous Steps

When Behat finds two or more definitions that match a single step, this step is marked as ambiguous.

Consider your FeatureContext has following code:

<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    /** @Given /^.* step with .*$/ */
    public function someStepWithArgument()
    {
    }

    /** @Given /^number step with (\d+)$/ */
    public function numberStepWith($argument1)
    {
    }
}

Running your feature with this context will result in:

../_images/definitions-ambiguous.png

Behat will not make a decision about which definition to execute. That’s your job! But as you can see, Behat will provide useful information to help you eliminate such problems.

Redundant Step Definitions

Behat will not let you define a step expression’s corresponding regular expression more than once. For example, look at the two @Given regular expressions defined in this feature context:

<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    /** @Given /^number step with (\d+)$/ */
    public function workWithNumber($number1)
    {
    }

    /** @Given /^number step with (\d+)$/ */
    public function workDifferentlyWithNumber($number1)
    {
    }
}

Executing Behat with this feature context will result in a Redundant exception being thrown:

../_images/definitions-redundant.png

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 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 annotation style as step definition methods, but instead use the @Transform keyword, followed by a matching regular expression.

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

<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    /**
     * @Transform /^(\d+)$/
     */
    public function castStringToNumber($string)
    {
        return intval($string);
    }

    /**
     * @Then /^a user '([^']+)', should have (\d+) followers$/
     */
    public function assertUserHasFollowers($name, $count)
    {
        if ('integer' !== gettype($count)) {
            throw new Exception('Integer expected');
        }
    }
}

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:

<?php

use Behat\Behat\Context\BehatContext;

class FeatureContext extends BehatContext
{
    /**
     * @Transform /^user (.*)$/
     */
    public function castUsernameToUser($username)
    {
        return new User($username);
    }

    /**
     * @Then /^a '(user [^']+)', should have (\d+) followers$/
     */
    public function assertUserHasFollowers(User $name, $count)
    {
        if ('integer' !== gettype($count)) {
            throw new Exception('Integer expected');
        }
    }
}

Transforming Tables

Let’s pretend we have written the following feature:

# 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:

<?php

use Behat\Behat\Context\BehatContext;
use Behat\Gherkin\Node\TableNode;

class FeatureContext extends BehatContext
{
    /**
     * @Given /^the following users$/
     */
    public function pushUsers(TableNode $usersTable)
    {
        $users = array();
        foreach ($usersTable->getHash() 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::

<?php

use Behat\Behat\Context\BehatContext;
use Behat\Gherkin\Node\TableNode;

class FeatureContext extends BehatContext
{
    /**
     * @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.

Step Execution Chaining

Sometimes it might be useful to pass execution flow from one step to another. For example, if during step definition execution you found that it might be better to call another step to keep from duplicating code, you can just return a step imitator object (substep) from a definition method:

<?php

use Behat\Behat\Context\BehatContext,
    Behat\Behat\Context\Step\Then;
use Behat\Gherkin\Node\TableNode;

class FeatureContext extends BehatContext
{
    /**
     * @Then /^(?:|I )should be on "(?P<page>[^"]+)"$/
     */
    public function assertPageAddress($page)
    {
        // check, that $page is equal to current page
    }

    /**
     * @Then /^the url should match "(?P<pattern>[^"]+)"$/
     */
    public function assertUrlRegExp($pattern)
    {
        if (!preg_match('/^\/.*\/$/', $pattern)) {
            return new Then("I should be on \"$pattern\"");
        }

        // do regex assertion
    }
}

Notice that when we do not provide a regular expression to Then the url should match "..." in the step definition assertUrlRegExp(), it returns a new Behat\Behat\Context\Step\Then instance. When a step definition returns such object, it finds and executes the step definition that matches the step text provided as that object’s argument.

Tip

There are three predefined substep classes you can to use:

  1. Behat\Behat\Context\Step\Given
  2. Behat\Behat\Context\Step\When
  3. Behat\Behat\Context\Step\Then

These are the same steps used to annotate step definitions.

You can also return steps with multiline arguments. You can even pass in a table as an argument:

/**
 * @Given /^I have the initial table$/
 */
public function table()
{
    $table = new Behat\Gherkin\Node\TableNode(<<<TABLE
        | username | password |
        | everzet  | 123456   |
TABLE
    );

    return new Given('I have users:', $table);
}

Note

Steps executed as a chain will throw an exception for all result types except for Successful. This means you’ll never get snippets out of steps, called only through execution chain!

As of 2.0.4, if you want to pass more than one step in an execution chain, just return an array of substep instances:

/**
 * @Given /I entered "([^"]*)" and expect "([^"]*)"/
 */
public function complexStep($number, $result)
{
    return array(
        new Step\Given("I have entered \"$number\""),
        new Step\When("I press +"),
        new Step\Then("I should see \"$result\" on the screen")
    );
}