Behat
Behat
Behat
Release 3.0.12
Contents
Quick Intro
1.1 Quick Intro to Behat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Guides
2.1 Writing Features - Gherkin Language . . . .
2.2 Defining Reusable Actions - Step Definitions
2.3 Hooking into the Test Process - Hooks . . .
2.4 Testing Features - FeatureContext Class
2.5 Closures as Definitions and Hooks . . . . . .
2.6 Command Line Tool - behat . . . . . . . .
2.7 Configuration - behat.yml . . . . . . . .
3
3
.
.
.
.
.
.
.
15
15
23
37
43
47
50
57
Cookbook
3.1 Developing Web Applications with Behat and Mink . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Migrating from Behat 1.x to 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.3 Using Spin Functions for Slow Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
63
70
75
Useful Resources
79
81
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
ii
Behat is an open source behavior-driven development framework for PHP 5.3 and 5.4. What is behavior-driven
development, you ask? Its the idea that you start by writing human-readable sentences that describe a feature of your
application and how it should work, and only then implement this behavior in software.
For example, imagine youre about to create the famous UNIX ls command. Before you begin, you describe how the
feature should work:
Feature: ls
In order to see the directory structure
As a UNIX user
I need to be able to list the current directorys contents
Scenario:
Given I am in a directory "test"
And I have a file named "foo"
And I have a file named "bar"
When I run "ls"
Then I should get:
"""
bar
foo
"""
As a developer, your work is done as soon as youve made the ls command behave as described in the Scenario.
Now, wouldnt it be cool if something could read this sentence and use it to actually run a test against the ls command?
Hey, thats exactly what Behat does! As youll see, Behat is easy to learn, quick to use, and will put the fun back into
your testing.
Note: Behat was inspired by Rubys Cucumber project, especially its syntax (called Gherkin).
Contents
Contents
CHAPTER 1
Quick Intro
To become Behater in 20 minutes, just dive into the quick-start guide and enjoy!
In this tutorial, well show you how Behat can execute this simple story as a test that verifies that the ls commands
works as described.
Thats it! Behat can be used to test anything, including web-related behavior via the Mink library.
Note: If you want to learn more about the philosophy of testing the behavior of your application, see Whats in a
Story?
Note: Behat was inspired by Rubys Cucumber project.
1.1.1 Installation
Behat is an executable that youll run from the command line to execute your stories as tests. Before you begin, ensure
that you have at least PHP 5.3.1 installed.
Method #1 (Composer)
The simplest way to install Behat is through Composer.
Create composer.json file in the project root:
{
"require": {
"behat/behat": "2.4.*@stable"
},
"minimum-stability": "dev",
"config": {
"bin-dir": "bin/"
}
}
Note: Composer uses GitHub zipball service by default and this service is known for outages from time to time. If
you get
The ... file could not be downloaded (HTTP/1.1 502 Bad Gateway)
Method #2 (PHAR)
Also, you can use behat phar package:
$ wget https://github.com/downloads/Behat/Behat/behat.phar
Now you can execute Behat by simply running phar archive through php:
$ php behat.phar
Method #3 (Git)
You can also clone the project with Git by running:
$ git clone git://github.com/Behat/Behat.git && cd Behat
$ git submodule update --init
The behat --init will create a features/ directory with some basic things to get your started.
Define your Feature
Everything in Behat always starts with a feature that you want to describe and then implement. In this example, the
feature will be the ls command, which can be thought of as one feature of the whole UNIX system. Since the feature
is the ls command, start by creating a features/ls.feature file:
# features/ls.feature
Feature: ls
In order to see the directory structure
As a UNIX user
I need to be able to list the current directorys contents
Every feature starts with this same format: a line naming the feature, followed by three lines that describe the benefit,
the role and the feature itself. And while this section is required, its contents arent actually important to Behat or your
eventual test. This section is important, however, so that each feature is described consistently and is readable by other
people.
Define a Scenario
Next, add the following scenario to the end of the features/ls.feature file:
Scenario: List 2 files in a directory
Given I am in a directory "test"
And I have a file named "foo"
And I have a file named "bar"
When I run "ls"
Then I should get:
"""
bar
foo
"""
Tip: The special """ syntax seen on the last few lines is just a special syntax for defining steps on multiple lines.
Dont worry about it too much for now.
Each feature is defined by one or more scenarios, which explain how that feature should act under different conditions. This is the part that will be transformed into a test. Each scenario always follows the same basic format:
Scenario: Some description of the scenario
Given [some context]
When [some event]
Then [outcome]
Each part of the scenario - the context, the event, and the outcome - can be extended by adding the And or But
keyword:
Scenario: Some description of the scenario
Given [some context]
And [more context]
When [some event]
And [second event occurs]
Then [outcome]
And [another outcome]
But [another outcome]
Theres no actual difference between Then, And, But or any of the other words that start each line. These keywords
are all made available so that your scenarios are natural and readable.
Executing Behat
Youve now defined the feature and one scenario for that feature. Youre ready to see Behat in action! Try executing
Behat from inside your ls_project directory:
$ behat
Lets use Behats advice and add the following to the features/bootstrap/FeatureContext.php file,
renaming $argument1 to $dir, simply for clarity:
# features/bootstrap/FeatureContext.php
<?php
use Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
class FeatureContext extends BehatContext
{
/**
* @Given /^I am in a directory "([^"]*)"$/
*/
public function iAmInADirectory($dir)
{
if (!file_exists($dir)) {
mkdir($dir);
}
chdir($dir);
}
}
Basically, weve started with the regular expression suggested by Behat, which makes the value inside the quotations
(e.g. test) available as the $dir variable. Inside the method, we simple create the directory and move into it.
Repeat this for the other three missing steps so that your FeatureContext.php file looks like this:
# features/bootstrap/FeatureContext.php
<?php
use Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
class FeatureContext extends BehatContext
{
private $output;
/** @Given /^I am in a directory "([^"]*)"$/ */
public function iAmInADirectory($dir)
{
if (!file_exists($dir)) {
mkdir($dir);
}
chdir($dir);
}
/** @Given /^I have a file named "([^"]*)"$/ */
public function iHaveAFileNamed($file)
{
touch($file);
}
/** @When /^I run "([^"]*)"$/ */
public function iRun($command)
{
exec($command, $output);
$this->output = trim(implode("\n", $output));
}
/** @Then /^I should get:$/ */
public function iShouldGet(PyStringNode $string)
{
if ((string) $string !== $this->output) {
throw new Exception(
"Actual output is:\n" . $this->output
);
}
}
}
Note: When you specify multi-line step arguments - like we did using the triple quotation syntax (""") in the above
scenario, the value passed into the step function (e.g. $string) is actually an object, which can be converted into a
string using (string) $string or $string->getRaw().
Great! Now that youve defined all of your steps, run Behat again:
$ behat
Success! Behat executed each of your steps - creating a new directory with two files and running the ls command and compared the result to the expected result.
Of course, now that youve defined your basic steps, adding more scenarios is easy. For example, add the following to
your features/ls.feature file so that you now have two scenarios defined:
Scenario: List 2 files in a directory with the -a option
Given I am in a directory "test"
And I have a file named "foo"
And I have a file named ".bar"
When I run "ls -a"
Then I should get:
"""
.
..
.bar
foo
"""
Run Behat again. This time, itll run two tests, and both will pass.
10
Thats it! Now that youve got a few steps defined, you can probably dream up lots of different scenarios to write
for the ls command. Of course, this same basic idea could be used to test web applications, and Behat integrates
beautifully with a library called Mink to do just that.
Of course, theres still lots more to learn, including more about the Gherkin syntax (the language used in the
ls.feature file).
Everything related to Behat will live inside the features directory, which is composed of three basic areas:
1. features/ - Behat looks for *.feature files here to execute
2. features/bootstrap/ - Every *.php file in that directory will be autoloaded by Behat before any actual
steps are executed
3. features/bootstrap/FeatureContext.php - This file is the context class in which every scenario
step will be executed
11
Supported languages include (but are not limited to) fr, es, it and, of course, the english pirate dialect en-pirate.
Keep in mind, that any language, different from en should be explicitly marked with # language:
at the beginning of your *.feature file:
... comment
# language: fr
Fonctionnalit: ...
...
You can read more about features and Gherkin language in Writing Features - Gherkin Language guide.
A few pointers:
12
Tip: Behat doesnt come with its own assertion tool, but you can use any proper assertion tool out there. Proper
assertion tool is a library, which assertions throw exceptions on fail. For example, if youre 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);
}
}
In the same way, any step that does not throw an exception will be seen by Behat as passing.
You can read more about step definitions in Defining Reusable Actions - Step Definitions guide.
13
One of the handiest things it does it to show you all of the step definitions that you have configured in your system.
This is an easy way to recall exactly how a step you defined earlier is worded:
$ behat -dl
You can read more about Behat CLI in Command Line Tool - behat guide.
14
CHAPTER 2
Guides
The parser divides the input into features, scenarios and steps. Lets walk through the above example:
1. Feature: Some terse yet descriptive text of what is desired starts the feature and
gives it a title. Learn more about features in the Features section.
15
2. Behat does not parse the next 3 lines of text. (In order to... As an... I want to...). These lines simply provide
context to the people reading your feature, and describe the business value derived from the inclusion of the
feature in your software.
3. Scenario: Some determinable business situation starts the scenario, and contains a description of the scenario. Learn more about scenarios in the Scenarios section.
4. The next 7 lines are the scenario steps, each of which is matched to a regular expression defined elsewhere.
Learn more about steps in the Steps section.
5. Scenario:
When youre executing the feature, the trailing portion of each step (after keywords like Given, And, When, etc) is
matched to a regular expression, which executes a PHP callback function. You can read more about steps matching
and execution in Defining Reusable Actions - Step Definitions.
2.1.2 Features
Every *.feature file conventionally consists of a single feature. Lines starting with the keyword Feature: (or
its localized equivalent) followed by three indented lines starts a feature. A feature usually contains a list of scenarios.
You can write whatever you want up until the first scenario, which starts with Scenario: (or localized equivalent)
on a new line. You can use tags to group features and scenarios together, independent of your file and directory
structure.
Every scenario consists of a list of steps, which must start with one of the keywords Given, When, Then, But or
And (or localized one). Behat treats them all the same, but you shouldnt. Here is an example:
Feature: Serve coffee
In order to earn money
Customers should be able to
buy coffee at all times
Scenario: Buy last coffee
Given there are 1 coffees left in the machine
And I have deposited 1 dollar
When I press the coffee button
Then I should be served a coffee
In addition to basic scenarios, feature may contain scenario outlines and backgrounds.
2.1.3 Scenarios
Scenario is one of the core Gherkin structures. Every scenario starts with the Scenario: keyword (or localized
one), followed by an optional scenario title. Each feature can have one or more scenarios, and every scenario consists
of one or more steps.
The following scenarios each have 3 steps:
Scenario: Wilson posts to his own blog
Given I am logged in as Wilson
When I try to post to "Expensive Therapy"
Then I should see "Your article was published."
Scenario: Wilson fails to post to somebody elses blog
Given I am logged in as Wilson
When I try to post to "Gregs anti-tax rants"
Then I should see "Hey! Thats not your blog!"
16
Chapter 2. Guides
Scenario Outlines allow us to more concisely express these examples through the use of a template with placeholders:
Scenario Outline: Eating
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
Examples:
| start | eat | left |
| 12
| 5 | 7
|
| 20
| 5 | 15 |
The Scenario outline steps provide a template which is never directly run. A Scenario Outline is run once for each row
in the Examples section beneath it (not counting the first row of column headers).
The Scenario Outline uses placeholders, which are contained within < > in the Scenario Outlines steps. For example:
Given <Im a placeholder and Im ok>
Think of a placeholder like a variable. It is replaced with a real value from the Examples: table row, where the
text between the placeholder angle brackets matches that of the table column header. The value substituted for the
placeholder changes with each subsequent run of the Scenario Outline, until the end of the Examples table is reached.
Tip: You can also use placeholders in Multiline Arguments.
Note: Your step definitions will never have to match the placeholder text itself, but rather the values replacing the
placeholder.
So when running the first row of our example:
Scenario Outline: controlling order
Given there are <start> cucumbers
When I eat <eat> cucumbers
Then I should have <left> cucumbers
Examples:
17
2.1.5 Backgrounds
Backgrounds allows you to add some context to all scenarios in a single feature. A Background is like an untitled
scenario, containing a number of steps. The difference is when it is run: the background is run before each of your
scenarios, but after your BeforeScenario hooks (Hooking into the Test Process - Hooks).
Feature: Multiple site support
Background:
Given a global administrator named "Greg"
And a blog named "Gregs anti-tax rants"
And a customer named "Wilson"
And a blog named "Expensive Therapy" owned by "Wilson"
Scenario: Wilson posts to his own blog
Given I am logged in as Wilson
When I try to post to "Expensive Therapy"
Then I should see "Your article was published."
Scenario: Greg posts to a clients blog
Given I am logged in as Greg
When I try to post to "Expensive Therapy"
Then I should see "Your article was published."
2.1.6 Steps
Features consist of steps, also known as Givens, Whens and Thens.
Behat doesnt technically distinguish between these three kind of steps. However, we strongly recommend that you
do! These words have been carefully selected for their purpose, and you should know what the purpose is to get into
the BDD mindset.
Robert C. Martin has written a great post about BDDs Given-When-Then concept where he thinks of them as a finite
state machine.
Givens
The purpose of Given steps is to put the system in a known state before the user (or external system) starts interacting
with the system (in the When steps). Avoid talking about user interaction in givens. If you have worked with use cases,
givens are your preconditions.
Note: Two good examples of using Givens are:
18
Chapter 2. Guides
Authenticate a user (An exception to the no-interaction recommendation. Things that happened earlier are
ok):
Given I am logged in as "Everzet"
Tip: Its ok to call into the layer inside the UI layer here (in symfony: talk to the models).
And for all the symfony users out there, we recommend using a Given step with a tables arguments to set up records
instead of fixtures. This way you can read the scenario all in one place and make sense out of it without having to
jump between files:
Given there are users:
| username | password | email
|
| everzet | 123456
| [email protected] |
| fabpot
| 22@222
| [email protected] |
Whens
The purpose of When steps is to describe the key action the user performs (or, using Robert C. Martins metaphor,
the state transition).
Note: Two good examples of Whens use are:
Interact with a web page (the Mink library gives you many web-friendly When steps out of the box):
When
When
When
When
I
I
I
I
am on "/some/page"
fill "username" with "everzet"
fill "password" with "123456"
press "login"
Interact with some CLI library (call commands and record output):
When I call "ls -la"
Thens
The purpose of Then steps is to observe outcomes. The observations should be related to the business value/benefit in
your feature description. The observations should inspect the output of the system (a report, user interface, message,
command output) and not something deeply buried inside it (that has no business value and is instead part of the
implementation).
Verify that something related to the Given+When is (or is not) in the output
Check that some external system has received the expected message (was an email with specific content successfully sent?)
When I call "echo hello"
Then the output should be "hello"
19
Note: While it might be tempting to implement Then steps to just look in the database resist the temptation. You
should only verify output that is observable by the user (or external system). Database data itself is only visible
internally to your application, but is then finally exposed by the output of your system in a web browser, on the
command-line or an email message.
And, But
If you have several Given, When or Then steps you can write:
Scenario: Multiple Givens
Given one thing
Given an other thing
Given yet an other thing
When I open my eyes
Then I see something
Then I dont see something else
Or you can use And or But steps, allowing your Scenario to read more fluently:
Scenario: Multiple Givens
Given one thing
And an other thing
And yet an other thing
When I open my eyes
Then I see something
But I dont see something else
If you prefer, you can indent scenario steps in a more programmatic way, much in the same way your actual code is
indented to provide visual context:
Scenario: Multiple Givens
Given one thing
And an other thing
And yet an other thing
When I open my eyes
Then I see something
But I dont see something else
Behat interprets steps beginning with And or But exactly the same as all other steps. It doesnt differ between them you should!
Chapter 2. Guides
Scenario:
Given the
| name
| Aslak
| Joe
| Bryan
|
|
|
|
Note: Dont be confused with tables from scenario outlines - syntactically they are identical, but have a different
purpose.
Tip: A matching definition for this step looks like this:
/**
* @Given /the following people exist:/
*/
public function thePeopleExist(TableNode $table)
{
$hash = $table->getHash();
foreach ($hash as $row) {
// $row[name], $row[email], $row[phone]
}
}
Note: A table is injected into a definition as a TableNode object, from which you can get hash by columns
(TableNode::getHash() method) or by rows (TableNode::getRowsHash()).
PyStrings
Multiline Strings (also known as PyStrings) are handy for specifying a larger piece of text. This is done using the
so-called PyString syntax. The text should be offset by delimiters consisting of three double-quote marks (""") on
lines by themselves:
Scenario:
Given a blog post named "Random" with:
"""
Some Title, Eh?
===============
Here is the first paragraph of my blog post.
Lorem ipsum dolor sit amet, consectetur adipiscing
elit.
"""
Note: The inspiration for PyString comes from Python where """ is used to delineate docstrings, much in the way
/* ... */ is used for multiline docblocks in PHP.
Tip: In your step definition, theres no need to find this text and match it in your regular expression. The text will
automatically be passed as the last argument into the step definition method. For example:
/**
* @Given /a blog post named "([^"]+)" with:/
*/
public function blogPost($title, PyStringNode $markdown)
{
21
$this->createPost($title, $markdown->getRaw());
}
Note: PyStrings are stored in a PyStringNode instance, which you can simply convert to a string with (string)
$pystring or $pystring->getRaw() as in the example above.
Note: Indentation of the opening """ is not important, 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
definitions callback will be de-indented according to the opening """. Indentation beyond the column of the opening
""" will therefore be preserved.
2.1.8 Tags
Tags are a great way to organize your features and scenarios. Consider this example:
@billing
Feature: Verify billing
@important
Scenario: Missing product description
Scenario: Several products
A Scenario or Feature can have as many tags as you like, just separate them with spaces:
@billing @bicker @annoy
Feature: Verify billing
Note: If a tag exists on a Feature, Behat will assign that tag to all child Scenarios and Scenario Outlines
too.
Note: Keep in mind that any language different from en should be explicitly marked with a # language:
comment at the beginning of your *.feature file:
...
# language: fr
Fonctionnalit: ...
...
This way your features will hold all the information about its content type, which is very important for methodologies
like BDD, and will also give Behat the ability to have multilanguage features in one suite.
22
Chapter 2. Guides
After you run this command, Behat will set up a features directory inside your project:
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.
23
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. Its that simple!
Behat uses phpDoc annotations to bind regular expressions to FeatureContext methods:
/**
* @When /^I do something with "([^"]*)"$/
*/
public function someMethod($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 steps 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 cant 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 definitions 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:
24
Chapter 2. Guides
/**
* @When /^I do something with "([^"]*)" and with (\d+)$/
*/
public function someMethod($stringArgument, $numberArgument) {}
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. Isnt that cool?
25
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!
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.
Lets 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, youll see all steps passed and are marked green:
26
Chapter 2. Guides
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.
Lets 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, youll get 2 undefined steps that are marked yellow:
27
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.
Lets 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);
}
28
Chapter 2. Guides
When you run your feature, youll get 1 pending step that is marked yellow:
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.
Lets pretend that your FeatureContext has the 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)
{
}
29
When you run your feature, youll get 1 failing step that is marked red:
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 youre 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
30
Chapter 2. Guides
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:
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)
{
}
}
31
Behat will not make a decision about which definition to execute. Thats 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 expressions 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:
32
Chapter 2. Guides
Lets 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);
}
/**
33
Transforming Tables
Lets 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
|
|
|
|
|
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.
34
Chapter 2. Guides
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.
35
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
objects 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 youll
never get snippets out of steps, called only through execution chain!
36
Chapter 2. Guides
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")
);
}
37
38
Chapter 2. Guides
Heres a good question for you to consider: how does Behat print suite execution information into the console and
collect statistics to be printed after? The answer is hidden in the Observer Design Pattern.
On every point of execution, testers initialize special objects called events (in green text in the diagram) and send them
to a special EventDispatcher object (green square in the diagram). Other objects, called listeners (blue squares in
the diagram) register on EventDispatcher to be able to receive those events. EventDispatcher automatically
dispatches received events to registered listeners that are capable of handling a particular event type.
Thats how statistic collectors, formatters and hooks work. They just provide listeners to some type of Behat lifetime
events.
2.3.2 Hooks
Behat hooks are a simple way to execute code when core Behat events occur. Behat provides 8 event types for you to
hook into:
1. The BeforeSuite event happens before any feature in the suite runs. For example, you could use this to setup
the project you are testing. This event comes with an instance of the Behat\Behat\Event\SuiteEvent
class.
2. The AfterSuite event happens after all features in the suite have run. This event is used internally for
statistics printing. This event comes with an instance of the Behat\Behat\Event\SuiteEvent class.
3. The BeforeFeature event occurs before a feature runs.
Behat\Behat\Event\FeatureEvent class.
4. The AfterFeature event occurs after Behat finishes executing a feature. This event comes with an instance
of the Behat\Behat\Event\FeatureEvent class.
5. The BeforeScenario event occurs before a specific scenario will run. This event comes with an instance of
the Behat\Behat\Event\ScenarioEvent class.
6. The AfterScenario event occurs after Behat finishes executing a scenario. This event comes with an instance of the Behat\Behat\Event\ScenarioEvent class.
7. The BeforeStep event occurs before a specific step runs. This event comes with an instance of the
Behat\Behat\Event\StepEvent class.
8. The AfterStep event occurs after Behat finishes executing a step. This event comes with an instance of the
Behat\Behat\Event\StepEvent class.
You can hook into any of these events by using annotations on methods in your FeatureContext class:
/**
* @BeforeSuite
*/
public static function prepare(SuiteEvent $event)
{
// prepare system for test suite
// before it runs
}
We use annotations as we did before with definitions. Simply use the annotation of the name of the event you want to
hook into (e.g. @BeforeSuite).
39
/** @BeforeSuite */
public static function setup(SuiteEvent $event)
{
}
/** @AfterSuite */
public static function teardown(SuiteEvent $event)
{
}
40
Chapter 2. Guides
41
getResult() - returns the resulting (highest) step run code. This returned value is one of StepEvent constants: 4 when an examples row has failed steps, 3 when a row has undefined steps, 2 when a row has pending
steps and 0 when all steps are passing.
isSkipped() - returns true if the scenario has skipped steps (steps that follow pending, undefined or
failed steps).
42
Chapter 2. Guides
/**
* @BeforeScenario @database,@orm
*/
public function cleanDatabase()
{
// clean database before
// @database OR @orm scenarios
}
Use the && tag to execute a hook only when it has all provided tags:
/**
* @BeforeScenario @database&&@fixtures
*/
public function cleanDatabaseFixtures()
{
// clean database fixtures
// before @database @fixtures
// scenarios
}
43
Behat will create a few directories and a skeleton FeatureContext class inside your project:
# 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;
/**
* Features context.
*/
class FeatureContext extends BehatContext
{
}
Chapter 2. Guides
1. Every scenario is isolated from each other scenarios context. You can do almost anything inside your scenario
context instance without the fear of affecting other scenarios - every scenario gets its own context instance.
2. Every step in a single scenario is executed inside a common context instance. This means you can set private
instance variables inside your @Given steps and youll be able to read their new values inside your @When and
@Then steps.
Note: PHP does not yet support horizontal reusability in its core feature set. While this functionality, called traits, is
on the roadmap for PHP 5.4, Behat provides subcontexts as a stop-gap solution to achieve horizontal reusability until
this functionality is available in a stable PHP release.
Behat\Behat\Context\BehatContext provides a special useContext() instance method allowing you
to connect one or more subcontext instances to your main FeatureContext class.
The first argument to the useContext() method is always a subcontext alias (subcontext_alias), allowing
you to later access any subcontext from another subcontext.
SubContext instances should follow the same Context Class Requirements as your main FeatureContext:
#features/bootstrap/SubContext.php
<?php
use Behat\Behat\Context\BehatContext;
class SubContext extends BehatContext
{
public function __construct(array $parameters)
{
// do subcontext initialization
}
}
All step definitions and hooks defined in a subcontext are parsed by Behat and available right away to use in your
features.
45
If you need to inject parameters or make other changes to your subcontext object, do so before passing it into
useContext():
# features/bootstrap/FeatureContext.php
<?php
use Behat\Behat\Context\BehatContext;
class FeatureContext extends BehatContext
{
public function __construct(array $parameters)
{
$this->useContext(subcontext_alias, new SubContext(array(
/* custom params */
)));
}
}
46
Chapter 2. Guides
# features/bootstrap/FeatureContext.php
<?php
use Behat\Behat\Context\ContextInterface;
class FeatureContext implements ContextInterface
{
private $subcontext;
public function __construct()
{
$this->subcontext = new SubContext();
}
public function getSubcontexts()
{
return array($this->subcontext);
}
public function getSubcontextByClassName($className)
{
if (SubContext === $className) {
return $this->subcontext;
}
}
}
47
To
use
closures,
your
FeatureContext
Behat\Behat\Context\ClosuredContextInterface. :
must
implement
the
interface
<?php
namespace Behat\Behat\Context;
interface ClosuredContextInterface extends ContextInterface
{
function getStepDefinitionResources();
function getHookDefinitionResources();
}
Given this example, Behat will try to load step definitions from features/steps/basic_steps.php and
hooks from features/support/hooks.php.
48
Chapter 2. Guides
In the previous example, we call a definition generator. This generator maps the provided closure to the given regular
expression.
Just like their annotation counterparts, Behat does not distinguish between keyword methods (Given, When, Then)
available via $steps, and uses them only to make your definition files more readable. In fact, the name of the method
doesnt matter one bit!
<?php
$steps->SomeUnexistentKeyword(/^I have ordered hot "([^"]*)"$/,
function($world, $arg1) {
throw new Behat\Behat\Exception\PendingException();
}
);
The first argument to the definition generator is a regular expression, and the second argument is a closure that would
be called when the regular expression matches your Gherkin step.
The first argument to the provided closure should always be an instance of FeatureContext. This is done for you
to be able to share context information between scenario steps. Classes in PHP have $this, but closures have no
concept of $this (at least until PHP 5.4):
<?php
$steps->Given(/^some context$/, function($world) {
$world->someVar = someVal;
});
$steps->Then(/^outcome$/, function($world) {
// $world->someVar === someVal
});
Note: $world is always an instance of the main FeatureContext class. This means you should provide missing
methods and properties for your subcontexts:
# features/bootstrap/FeatureContext.php
<?php
class FeatureContext
{
public function __construct(array $parameters)
{
$this->useContext(new SubContext($this));
}
public function doSomething()
{
// ...
}
}
# features/bootstrap/SubContext.php
<?php
class SubContext
49
{
private $mainContext;
public function __construct(FeatureContext $context)
{
$this->mainContext = $context;
}
public function doSomething()
{
$this->mainContext->doSomething();
}
}
2.5.3 Hooks
Every *.php path returned by getHookDefinitionResources() will be loaded with the variable $hooks
already defined.
Use the $hooks variable to define your hooks:
<?php
$hooks->beforeFeature(, function($event) {
// prepare feature
});
$hooks->afterFeature(, function($event) {
// teardown feature
});
You have the ability to call all hook types described in the Hooking into the Test Process - Hooks chapter. The only
difference is that the method names are camel-cased (e.g. @BeforeFeature becomes beforeFeature()).
The first argument to all hook generators, except beforeSuite and afterSuite, is a tag filter.
In other parts, closure hooks are the same as normal annotated hooks.
50
Chapter 2. Guides
51
$ behat -V
Running this command will setup a features directory and a skeleton FeatureContext class inside your
project:
You can use configuration files to configure your feature suite. By default, behat will try to load behat.yml and
config/behat.yml, but if you want to name your config file differently, tell behat about it with the --config
option:
$ behat --config custom-config-file.yml
Your configuration files can have multiple profiles (named configurations). You can run behat with a specific profile
by calling it with the --profile option:
$ behat --config behat.yml --profile ios
52
Chapter 2. Guides
Note: Some formatters, like junit, always require the --out option to be specified. The junit formatter
generates *.xml files for every feature, so it needs a destination directory to put these XML files into.
Also, you can specify multiple formats to be used by Behat with comma (,):
$ behat -f pretty,progress
In this case, default output will be used as output for both formatters. But if you want them to use different ones specify them with --out:
$ behat -f pretty,progress,junit --out ~/pretty.out,,xml
In this case, output of pretty formatter will be written to ~/pretty.out file, output of junit formatter will be written
to xml folder and progress formatter will just print to console. Empty out option (as in case with progress in example)
tells Behat to use stdout. So:
$ behat -f pretty,progress,junit --out ,progress.out,xml
Will print pretty output instead, but will write progress output to progress.out file.
2.6. Command Line Tool - behat
53
Behat tries hard to identify if your terminal supports colors or not, but sometimes it still fails. In such cases, you can
force behat to use colors (or not) with the options --ansi or --no-ansi, respectively:
$ behat --no-ansi
Behat prints suite execution time information after each run. If you dont want this information, you can turn it off
with the --no-time option:
$ behat --no-time
Also, there are a bunch of options to hide some default output information from the output:
--no-paths - hides paths after steps and scenarios.
--no-snippets - hides snippet proposals for undefined steps after statistics.
--snippets-paths - prints step information with snippets.
--no-multiline - hides multiline arguments (tables and pystrings) from pretty output.
By default, Behat prints scenario outlines the same as you define them:
The output is pretty minimal and enough for you to see some errors. But in some complex cases, it may be hard to
actually find failed steps in the output. To make this easier, behat offers the --expand option:
$ behat --expand
54
Chapter 2. Guides
Only execute the feature elements which match part of the given name or regex.
If the element is part of feature-definition behat executes the whole feature, otherwise one or more scenarios.
$ behat --rerun="return.log"
Save list of failed scenarios into new file or use existing file to run only scenarios from it.
If the file contains no scenarios, then all scenarios will executed.
This command will print an example feature for you to understand what keywords to use and where to use them in
your *.feature files:
55
If you write features in a language other than English, you can view a localized example feature in your language of
choice by using the --lang option:
$ behat --story-syntax --lang fr
Also, if you forgot what step definitions youve already implemented, or how to spell a particular step, behat will
print all available definitions by calling it with the -dl option:
$ behat -dl
Chapter 2. Guides
behat
behat
behat
behat
behat
--tags
--tags
--tags
--tags
--name
@orm,@database
ticket,723
@orm&&@fixtures
~@javascript
number has
The first command will run only features or scenarios which have either the @orm or the @database tag.
The second command will run only features or scenarios which have either the @ticket or the @723 tag.
The third command will run only features or scenarios with both the @orm and the @fixtures tags.
The forth command will run all features or scenarios except those, that marked with @javascript tag. In other
terms, ~ means excluding.
The fifth command will run only features and scenarios that contain number has in their title.
2.7.1 behat.yml
All configuration happens inside a single configuration file in the YAML format. Behat tries to load behat.yml or
config/behat.yml by default, or you can tell Behat where your config file is with the --config option:
$ behat --config custom-config.yml
All configuration parameters in that file are defined under a profile name root (default: for example). A profile
is just a custom name you can use to quickly switch testing configuration by using the --profile option when
executing your feature suite.
The default profile is always default. All other profiles inherit parameters from the default profile. If you only
need one profile, define all of your parameters under the default: root:
57
# behat.yml
default:
#...
Paths
The first configuration block is paths. Parameters under this configuration block tell Behat where to find your feature
and bootstrap files:
# behat.yml
default:
paths:
features: features
bootstrap: %behat.paths.features%/bootstrap
The features parameter defines where Behat will look for your *.feature files. The given directory will
be scanned recursivly.
The bootstrap parameter defines the directory from which Behat will automatically load all *.php files.
Tip: Notice the %behat.paths.features% placeholder. These strings are predefined configuration variables,
that you can use to build very flexible configurations.
A variable is a placeholder that consists of lower-case letters and starts/ends with a single %. These variables are your
current configuration parameters, which you can use to nest configurations. Usable variables are:
1. %behat.paths.base% - current working dir or configuration path (if configuration file exists and loaded).
2. %behat.paths.features% - features path.
3. %behat.paths.bootstrap% - bootstrap path.
Filters
Another very useful configuration block is the filters block. This block defines default filters (name or tag) for
your features. If you find yourself typing the same filters again and again from run to run, it would be more efficient
for you to define them as parameters:
# behat.yml
default:
filters:
tags: "@wip"
These filter parameters (name and tags) accept the same strings as the Behat --name or --tags parameters do.
Formatter
If you need to customize your output formatter, the formatter block is right for you:
# behat.yml
default:
formatter:
name:
parameters:
decorated:
verbose:
58
pretty
true
false
Chapter 2. Guides
time:
language:
output_path:
multiline_arguments:
#...
true
en
null
true
name defines the default output formatter name to use for your features. You could write a class name here so
Behat will use your custom class as the default output formatter, but be careful - this class should be accessible
by Behat and implement Behat\Behat\Formatter\FormatterInterface.
The parameters section defines additional parameters which will be provided into the formatter instance. As
you can see, all parameters from this section duplicate behat tool options. You can redefine behat formatter
defaults here. Also, this is the place to specify parameters for your custom formatters.
The YAML configuration file supports the same formatter parameters as the behat tool, so you can give multiple
options for e.g. different formatters. This is useful when run in a continuous integration (CI) environment, so you get
machine-readable output for JUnit as well as human-readable text in one single run.
An example that generates multiple output formats could look like this:
# behat.yml
ci:
formatter:
name:
parameters:
output_path:
pretty,junit,html
null,junit,behat_report.html
This will write pretty text output to the console, one XML file per feature to the junit directory and an HTML report to
the file behat_report.html. See each formatters documentation for details on what parameters are available, optional
or mandatory.
Colors
New in version 2.2.
As of version 2.2, you can configure Behat formatters to use specific output styles (colors).
default:
formatter:
name:
pretty
parameters:
output_styles:
comment: [ black, white, [ underscore ] ]
this will force Behat to print comments (key of the style) with black foreground (first parameter), white background
(second parameter) and as underscore (list of options - third parameter).
Styles available for redefinition:
undefined - style of undefined step
pending - style of pending step
pending_param - style of param in pending step
failed - style of failed step
failed_param - style of param in failed step
passed - style of passed step
passed_param - style of param in passed step
2.7. Configuration - behat.yml
59
Your\Custom\Context
http://test.mink.loc
class defines which class you want to use as the environment. This class should be accessible by Behat and
implement Behat\Behat\Context\ContextInterface.
parameters parameters is a simple array that will be passed into the constructor of your context class when
instantiated, which happens before each scenario.
2.7.2 Profiles
Profiles help you define different configurations for running your feature suite. Lets say we need 2 different configurations that share common options, but use different formatters. Our behat.yml might look like this:
# behat.yml
default:
context:
class:
Your\Custom\Context
wip:
filters:
tags:
"@wip"
formatter:
name:
progress
ci:
formatter:
name:
junit
parameters:
output_path: /var/tmp/junit
This file defines 2 additional profiles (additional to default). Every profile will use Your\Custom\Context as its
environment object, but the wip profile will run only scenarios with the @wip (work in progress) tag and will output
them with the progress formatter. The ci profile will run all features and output them with the junit formatter
to the /var/tmp/junit path.
To run each of these custom profiles, use the --profile option:
60
Chapter 2. Guides
2.7.3 Extensions
The extensions block allows you to activate extensions for your suite or for specific profile of the suite:
# behat.yml
default:
extensions:
Behat\Symfony2Extension\Extension: ~
mink:
extensions:
mink-extension.phar:
base_url: http://domain.org
api:
extensions:
Behat\WebApiExtension\Extension:
base_url: http://api.domain.org
2.7.4 Imports
The imports block allows you to share your feature suite configuration between projects and their test suites:
# behat.yml
imports:
- some_installed_pear_package_or_lib/behat.yml
- /full/path/to/custom_behat_config.yml
All files from the imports block will be loaded by Behat and merged into your behat.yml config.
You could setup default value for any option, that available for you in behat.yml. Just provide options in url format
(parseable by parse_str() php function). Behat will use those options as default ones and you will always be able
to redefine them with project behat.yml (it has higher priority).
61
62
Chapter 2. Guides
CHAPTER 3
Cookbook
Youll need something to simulate browser application. Scenario steps would simulate a user and the browser emulator
would simulate a browser with which the user interacts in order to talk to the web application.
Now the real problem. We have 2 completely different types of solutions:
Headless browser emulators - browser emulators that can be executed fully without GUI through console. Such
emulators can do HTTP requests and emulate browser applications on a high level (HTTP stack), but on a lower
level (JS, CSS) they are totally limited. They are much faster than real browsers, because you dont need to
parse CSS or execute JS in order to open pages or click links with them.
In-browser emulators - this type of emulator works with real browsers, taking full control of them and using
them as zombies for its testing needs. This way, youll have a standard, fully-configured, real browser, which
you will be able to control. CSS styling, JS and AJAX execution - all supported out of the box.
The problem is we need both these emulator types in order to do successful functional testing. Both these tools are
quite limited at some tasks, but succeed at others. For example, you cant use in-browser emulators for all tests in your
application, because this makes your tests become very slow. Also, you cant do AJAX with a headless browser.
You should use them both. But here comes a problem - these are very different tools and they have much different
APIs. Using both those APIs limits us very much and in case of Behat, this problem becomes even worse, because
now you have a single:
63
When I go to "/news.php"
And this step should be somehow executed through one or another browser emulator at will.
Here comes Mink. Mink is a browser emulator abstraction layer. It hides emulator differences behind a single,
consistent API.
Just some of the benefits:
1. Single, consistent API.
2. Almost zero configuration.
3. Support for both in-browser and headless browser emulators.
Note: Note that we also installed two Mink drivers - goutte and selenium2. Thats because by default, Composer
installation of Mink doesnt include any driver - you should choose what to use by yourself.
The easiest way to get started is to go with goutte and selenium2 drivers, but note that theres bunch of other
drivers available for Mink - read about them in Mink documentation.
Then download composer.phar and run install command:
$ curl http://getcomposer.org/installer | php
$ php composer.phar install
64
Chapter 3. Cookbook
And this executable will already autoload all the needed classes in order to activate MinkExtension through
behat.yml in the project root.
Now lets activate it:
# behat.yml
default:
extensions:
Behat\MinkExtension\Extension:
goutte: ~
selenium2: ~
It should show you all the predefined web steps as MinkExtension will automatically use the bundled MinkContext
if no user-defined context class is found.
Method #2 (PHAR)
Alternatively, you can use Behat, Mink and MinkExtension as PHAR packages.
Download Behat:
$ wget https://github.com/downloads/Behat/Behat/behat.phar
Download Mink:
$ wget https://github.com/downloads/Behat/Mink/mink.phar
Download MinkExtension:
$ wget https://github.com/downloads/Behat/MinkExtension/mink_extension.phar
Put them all in the same folder. After that, you will be able to run Behat with:
$ php behat.phar -h
65
It should show you all the predefined web steps as MinkExtension will automatically use the bundled MinkContext
if no user-defined context class found.
MinkContext for Behat requirements
MinkExtension comes bundled with MinkContext, which will be used automatically by Behat as main context class
if no user-defined context class found. Thats why behat -dl shows you step definitions even when you havent
created a custom FeatureContext class or even a features folder.
Or phar version:
66
Chapter 3. Cookbook
Thats it. Now you should create a specific scenario in order for it to be runnable through Selenium:
Scenario: Searching for a page with autocompletion
Given I am on "/wiki/Main_Page"
When I fill in "search" with "Behavior Driv"
And I wait for the suggestion box to appear
Then I should see "Behavior Driven Development"
Now, we need to tell Behat and Mink to run this scenario in a different session (with a different browser emulator).
Mink comes with a special hook, that searches @javascript or @mink:selenium2 tag before scenario and
switches the current Mink session to Selenium2 (in both cases). So, lets simply add this tag to our scenario:
@javascript
Scenario: Searching for a page with autocompletion
Given I am on "/wiki/Main_Page"
67
Thats because you have used custom Then I wait for the suggestion box to appear step, but have
not defined it yet. In order to do that, we will need to create our own FeatureContext class (at last).
Oh... Now Behat tells us that all steps are undefined. Whats happening there?
As weve created our own context class, MinkExtension stopped using its own bundled context class as main context
and Behat uses your very own FeatureContext instead, which of course doesnt have those Mink steps yet. Lets
add them.
There are multiple ways to bring the steps that are bundled with MinkExtension into your
own context class.
The simplest one is to use inheritance.
Just extend your context from
Behat\MinkExtension\Context\MinkContext instead of the base BehatContext.
Note that you will also have to do this if youve already been using Behat in your project, but without Mink, and are
now adding Mink to your testing:
<?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;
use Behat\MinkExtension\Context\MinkContext;
/**
* Features context.
*/
class FeatureContext extends MinkContext
{
}
68
Chapter 3. Cookbook
$ bin/behat -dl
That simple. We get the current session and send a JS command to wait (sleep) for 5 seconds or until the expression
in the second argument returns true. The second argument is a simple jQuery instruction.
Run the feature again and:
69
Voil!
Tip: Context isolation is a very important thing in functional tests. But restarting the browser after each scenario
could slow down your feature suite very much. So by default, Mink tries hard to reset your browser session without
reloading it (cleans all domain cookies).
In some cases it might not be enough (when you use http-only cookies for example). In that case, just add an
@insulated tag to your scenario. The browser in this case will be fully reloaded and cleaned (before scenario):
Feature: Some feature with insulated scenario
@javascript @insulated
Scenario: isolated scenario
#...
Chapter 3. Cookbook
the coolest change since 1.x. It made features suites much cleaner and extensible.
There were less than half-year between 1.0 and 2.0 releases? Some users already have big feature suites and dont
want to rewrite them once again. For such users, Behat 2.0 can become fully backward compatible with 3 very small
steps.
The easiest way to migrate is to move this code into FeatureContext class:
<?php
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
class FeatureContext extends BehatContext
{
public $someInitialVar = initial-val;
public function closureFunc()
{
// do something
}
}
As you might see, your someInitialVar become an instance variable and closureFunc() just an instance
method. You should move all your variables and methods carefully, changing all $world to $this in closure
methods.
It might be very hard and annoying work, especially on large projects. So, as you might expect, you have another
option:
<?php
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
class FeatureContext extends BehatContext
{
public $parameters = array();
public function __construct(array $parameters)
71
{
$this->parameters = $parameters;
if (file_exists($env = __DIR__./../support/env.php)) {
$world = $this;
require_once($env);
}
}
public function __call($name, array $args) {
if (isset($this->$name) && is_callable($this->$name)) {
return call_user_func_array($this->$name, $args);
} else {
$trace = debug_backtrace();
trigger_error(
Call to undefined method . get_class($this) . :: . $name .
in . $trace[0][file] .
on line . $trace[0][line],
E_USER_ERROR
);
}
}
}
With this context, youll be able to use your old env.php totally untouched. Thats it. Full BC with 1.x environment.
move
all
your
code
into
<?php
...
// require and load something here
class FeatureContext extends BehatContext
...
or you can leave bootstrap.php untouched and just tell FeatureContext.php to load it by itself:
<?php
...
if (file_exists($boot = __DIR__./../support/bootstrap.php)) {
require_once($boot);
}
class FeatureContext extends BehatContext
...
Thats it.
72
Chapter 3. Cookbook
Now, Behat will try to load all step definitions from out the features/steps/basic_steps.php file and hooks
from out the features/support/hooks.php.
Thats quite simple. But what if you have more than one definition file? Adding all this file into array by hands can
become tedious. But you always can use glob():
# features/bootstrap/FeatureContext.php
<?php
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\BehatContext;
73
/**
* Features context.
*/
class FeatureContext extends BehatContext implements ClosuredContextInterface
{
public function getStepDefinitionResources()
{
return glob(__DIR__./../steps/*.php);
}
public function getHookDefinitionResources()
{
return array(__DIR__ . /../support/hooks.php);
}
}
Yep. We will load all features/steps/*.php files automatically. Same as this were done in Behat 1.x.
74
Chapter 3. Cookbook
}
return array();
}
public function __call($name, array $args) {
if (isset($this->$name) && is_callable($this->$name)) {
return call_user_func_array($this->$name, $args);
} else {
$trace = debug_backtrace();
trigger_error(
Call to undefined method . get_class($this) . :: . $name .
in . $trace[0][file] .
on line . $trace[0][line],
E_USER_ERROR
);
}
}
}
You can just copynpaste this code into your features/bootstrap/FeatureContext.php and Behat2 will
magically start to work with your 1.x feature suite.
75
sleep(1);
}
}
This will create a loop, calling our anonymous function every second, until it returns true. To allow us to access the
FeatureContext object, well pass it into the function as a parameter.
This function will return whether #example element is visible or not. Our spin method will taken try to run this
function every second until the function returns true.
Because our spin method will also catch exceptions, we can also try actions that would normally throw an exception
and cause the test to fail.
$this->spin(function($context) {
$context->getSession()->getPage()->findById(example)->click();
return true;
});
If the #example element isnt available to click yet, the function will throw an exception, and this will be caught by
the try catch block in our spin method, and tried again in one second.
If no exception is thrown, the method will continue to run through and return true at the end.
Note: It is important to remember to return true at the end of the function, otherwise our spin method will continue
to try running the function.
76
Chapter 3. Cookbook
$backtrace = debug_backtrace();
throw new Exception(
"Timeout thrown by " . $backtrace[1][class] . "::" . $backtrace[1][function] . "()\n" .
$backtrace[1][file] . ", line " . $backtrace[1][line]
);
}
Now, if the function still isnt returning true after a minute, we will throw an exception stating where the test timed
out.
77
78
Chapter 3. Cookbook
CHAPTER 4
Useful Resources
79
80
CHAPTER 5
Once youre up and running with Behat, you can learn more about behavior-driven development via the following
links. Though both tutorials are specific to Cucumber, Behat shares a lot with Cucumber and the philosophies are one
and the same.
Dan Norths Whats in a Story?
Cucumbers Backgrounder
81