First steps with Behat

Warning : This blog post uses Behat 2.5. It is not all compatible with the ~3.0 version wich should be released on 20th of april 2014.

At the beginning of the year I decided it was time to give a try to BDD. Hence every new project I started from then on was done with BDD.
I found at the time lots of documentation/tutorials on the subject, but none was exactly what I was hoping for when I begun: a standalone step-by-step tutorial that goes from installation to having tested a full user story through BDD, code samples included.
To help others get started BDD with Behat, I want to share with you such a basic tutorial. We will develop a simple calculator that takes an operation (like “1+1”) and returns a result on a web page. At the end of this post you will have a functional calculator and you should know basics to start using Behat in your projects.

Setup the project

First of all we need to install dependencies in our project, we’ll use composer to do so:

# composer.json
{
    "autoload": {
        "psr-0": { "Calculator": "src/" }
    },
    "require": {
        "behat/behat": "*",
        "behat/mink-extension": "*",
        "behat/mink-goutte-driver": "*"
    },
    "minimum-stability": "dev",
    "config": {
        "bin-dir": "bin"
    }
}

Then install Composer (“curl -s https://getcomposer.org/installer | php”) and run “php composer.phar install”. Note that we also installed Mink and Goutte, that will allow us to easily test our web application.

Your first feature

A feature is a file that describes scenarios step by step.

To initialize our suite of features run the following command:

~/calculator $ bin/behat --init
+d features - place your *.feature files here
+d features/bootstrap - place bootstrap scripts and static files here
+f features/bootstrap/FeatureContext.php - place your feature related code here

Once the “features” folder is created you can start writing your features. Create a file “calculator feature” in your “features” dir:

#features/calculator.feature
Feature: Calculator calculates operations and returns it to you

    Scenario: Calculate 1+1 and return 2
        Given I am on "/index.php"
        When I fill in "operation" with "1+1"
        And I press "Ok"
        Then I should see "2" in the "#result" element

This needs some explanations. The first line contains the name of the feature after the keyword “Feature”.
Then we wrote the first scenario of the feature right after the ‘Scenario’ keyword and gave it a name. As in this example, you can add a description of your scenario. Then we added the steps of the scenario describing what happens. Each step is prefixed by a keyword “Given”, “When”, “And” and “Then”, this allows the scenario to be readable by both a human being and Behat.

Note: See the Gherkin language documentation on Behat’s website.

Ok, once the feature is written, we want to run behat to see the scenario fail:

~/calculator $ bin/behat
Feature: Calculator calculates operations and returns it to you

  Scenario: Calculate 1+1 and return 2 # features/calculator.feature:3
    Given I am on "/index.php"
    When I fill in "operation" with "1+1"
    And I press "Ok"
    Then I should see "2" in the "#result" element

1 scenario (1 undefined)
4 steps (4 undefined)
0m0.015s

You can implement step definitions for undefined steps with these snippets:

/**
 * @Given /^I am on "([^"]*)"$/
 */
public function iAmOn($arg1)
{
    throw new PendingException();
}

/**
 * @When /^I fill in "([^"]*)" with "([^"]*)"$/
 */
public function iFillInWith($arg1, $arg2)
{
    throw new PendingException();
}

/**
 * @Given /^I press "([^"]*)"$/
 */
public function iPress($arg1)
{
    throw new PendingException();
}

/**
 * @Then /^I should see "([^"]*)" in the "([^"]*)" element$/
 */
public function iShouldSeeInTheElement($arg1, $arg2)
{
    throw new PendingException();
}

Behat told you a lot of interesting stuff… let’s decompose it. First, it tells you that he cannot understand so far the step we gave it: you need to define what “I am on” means, for instance.
Then it generates templates to help you write your step definitions. Most of the time you will copy/paste these templates in your context class generated in the feature/bootstrap/FeatureContext.php file. Luckily, MinkExtension provides these definitions with the MinkContext class, so lets use it.

Before going further we will have to configure Behat through the behat.yml file:

# behat.yml
default:
    extensions:
        Behat\MinkExtension\Extension:
            base_url: 'http://127.0.0.1:4042' # this will be the url of our application
            goutte: ~

Then register the MinkContext as a subcontext in the FeatureContext class that Behat generated for you:

// features/bootstrap/FeatureContext.php
...
use Behat\MinkExtension\Context\MinkContext;

/**
 * Features context.
 */
class FeatureContext extends BehatContext
{
    /**
     * Initializes context.
     * Every scenario gets it's own context object.
     *
     * @param array $parameters context parameters (set them up through behat.yml)
     */
    public function __construct(array $parameters)
    {
        $this->useContext('mink', new MinkContext());
    }
}

Now if we run “bin/behat -dl” we will see the list of step definitions that our context is aware of.

Then running “bin/behat” you should see the first step fail:

Feature: Calculator calculates operations and returns it to you

  Scenario: Calculate 1+1 and return 2             # features/calculator.feature:3

    Given I am on "/index.php"                     # Behat\MinkExtension\Context\MinkContext::visit()
      [curl] 7: couldn't connect to host [url] http://127.0.0.1:4042/index.php
    When I fill in "operation" with "1+1"          # Behat\MinkExtension\Context\MinkContext::fillField()
    And I press "Ok"                               # Behat\MinkExtension\Context\MinkContext::pressButton()
    Then I should see "2" in the "#result" element # Behat\MinkExtension\Context\MinkContext::assertElementContainsText()

1 scenario (1 failed)
4 steps (3 skipped, 1 failed)
0m0.031s

To make the first step pass we have to make the “/index.php” url point to a real index.php file. Then you have to make Behat (throught Mink and Goutte) able to access this file. To do so you can setup a virtual host, but if you don’t want to spend most of your time setting it up you should open another tab in your console, download Symfttpd and add the following symfttpd.conf.php file in your project:

<?php
// symfttpd.conf.php

$options['project_type'] = 'php';
$options['project_web_dir'] = '.';

Note that you will need Lighttpd to be installed on your machine (to use Nginx with PHP-FPM run the command “path/to/symfttpd/bin/symfttpd init”).

Once Symfttpd is configured, run the following command:

~/calculator $ php symfttpd.phar spawn -t
Symfttpd - version 2.1.4
lighttpd started on 127.0.0.1, port 4042.

Available applications:
http://127.0.0.1:4042/index.php

Press Ctrl+C to stop serving.

If everything is going right you can now run the scenario again:

~/calculator $ bin/behat
Feature: Calculator calculates operations and returns it to you

  Scenario: Calculate 1+1 and return 2 # features/calculator.feature:3
    Given I am on "/index.php" # Behat\MinkExtension\Context\MinkContext::visit()
    When I fill in "operation" with "1+1" # Behat\MinkExtension\Context\MinkContext::fillField()
    Form field with id|name|label|value "operation" not found.
    And I press "Ok" # Behat\MinkExtension\Context\MinkContext::pressButton()
    Then I should see "2" in the "#result" element # Behat\MinkExtension\Context\MinkContext::assertElementContainsText()

1 scenario (1 failed)
4 steps (1 passed, 2 skipped, 1 failed)
0m1.154s

Now the first step is green, this means it passed, but the second one failed: “Form field with id|name label|value “operation” not found.” It means that we have some minimal code to write.

Green bar

In TDD we follow simple rules:

  • write a simple test
  • run all tests and fail
  • make a change
  • run the tests and succeed
  • finally refactor

The first step is already done since we wrote our scenario, and as we ran it and saw it fail step two is already done as well. Let’s make a change then.

To make the scenario succeed we need to write some html in the index.php file:

<html>
        <body>
                <form action="/" method="post">
                        <input type="text" name="operation" />
                        <button type="submit">Ok</button>
                </form>
        </body>
</html>

Then run Behat again:

~/calculator $ bin/behat
Feature: Calculator calculates operations and returns it to you

  Scenario: Calculate 1+1 and return 2             # features/calculator.feature:3
    Given I am on "/"                              # Behat\MinkExtension\Context\MinkContext::visit()
    When I fill in "operation" with "1+1"          # Behat\MinkExtension\Context\MinkContext::fillField()
    And I press "Ok"                               # Behat\MinkExtension\Context\MinkContext::pressButton()
    Then I should see "2" in the "#result" element # Behat\MinkExtension\Context\MinkContext::assertElementContainsText()
    Element matching css "#result" not found.

1 scenario (1 failed)
4 steps (3 passed, 1 failed)
0m0.048s

Two steps closer to success! The latest step is still failing, so to make it pass we will complete our HTML with hardcoded value (remember the TDD philosophy: find the simplest way to make the tests pass):

<html>
        <body>
                <form action="/" method="post">
                        <input type="text" name="operation" />
                        <button type="submit">Ok</button>
                </form>

                <div id="result">2</div>
        </body>
</html>

Then … you guessed it… run Behat:

~/calculator $ bin/behat
Feature: Calculator calculates operations and returns it to you

  Scenario: Calculate 1+1 and return 2             # features/calculator.feature:3
    Given I am on "/"                              # Behat\MinkExtension\Context\MinkContext::visit()
    When I fill in "operation" with "1+1"          # Behat\MinkExtension\Context\MinkContext::fillField()
    And I press "Ok"                               # Behat\MinkExtension\Context\MinkContext::pressButton()
    Then I should see "2" in the "#result" element # Behat\MinkExtension\Context\MinkContext::assertElementContainsText()

1 scenario (1 passed)
4 steps (4 passed)
0m0.041s

Green bar!! Ok this was the fourth step of our TDD process (run the tests and succeed), easy isn’t it?!

To go further we need to think about our calculator. Adding 1 to 1 was simple and we did it quite easily, but what if we want to add 2 to 3? Write the feature:

# feature/calculator.feature# ...Scenario: Calculate 2+3 and return 5
        Given I am on "/"When I fill in "operation" with "2+3"And I press "Ok"Then I should see "5" in the "#result" element

Of course, running Behat again, this scenario will fail as we hardcoded the result in our HTML. So we will need to change it without breaking the first scenario…
Here is the code:

<?php

$result = "";

// The operation was submitted
if (!empty($_POST)) {
        $operation = $_POST['operation'];

        $result = eval("return $operation;");
}

?>

<html>
        <body>
                <form action="/" method="post">
                        <input type="text" name="operation" />
                        <button type="submit">Ok</button>
                </form>

                <div id="result"><?php echo $result ?></div>
        </body>
</html>

Running Behat, everything is ok.

Does your calculator do something else than addition?

Yes! I will prove it! Add the following scenarios and run Behat again:

# features/calculator.featureFeature:

    # ...

    Scenario: Calculate 10-5 and return 5
        Given I am on "/"
        When I fill in "operation" with "10-5"And I press "Ok"Then I should see "5" in the "#result" elementScenario: Calculate 2*3 and return 6
        Given I am on "/"When I fill in "operation" with "2*3"And I press "Ok"Then I should see "6" in the "#result" elementScenario: Calculate 4/2 and return 2
        Given I am on "/"When I fill in "operation" with "4/2"And I press "Ok"Then I should see "2" in the "#result" element
~/calculator $ bin/behat --format=progress
................................

5 scenarios (5 passed)
20 steps (20 passed)
0m0.155s

So yes, our calculator can add, substract, multiply and divide.

Refactor: step 5 of TDD

Adding scenarios for each type of calculation we duplicate steps, let’s see how to keep our feature small but readable, using Scenario outlines:

Feature: Calculator calculates operations and returns it to you

    Scenario Outline: Calculate an operation and print the result
        Given I am on "/"
        When I fill in "operation" with "<operation>"And I press "Ok"Then I should see "<result>" in the "#result" elementExamples:
        | operation | result |        | 1+1       | 2      |        | 2+3       | 5      |        | 10-5      | 5      |        | 2*3       | 6      |        | 4/2       | 2      |

Run Behat:

~/calculator $ bin/behat --format=progress
............................

5 scenarios (5 passed)
20 steps (20 passed)
0m0.135s

Much cleaner, isn’t it? Yes, but we can improve its quality a little bit more. We tested that 1+1 equals 2 and 2+3 equals 5, we can remove one of these examples as it is quite the same.

Up to you!

If you want to go further you may add some functionalities to our Calculator. Improve the web interface, add buttons, separate view from controller…

Here are some links to go further with Behat, Mink and Gherkin.

Finally, if you are interested in BDD you should read this now classic blog post from Dan North’s blog and to learn more about TDD you should read the “Test-Driven Development by example” by Kent Beck.
Hope this article will help you start with Behat on your next PHP project!


You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us

  • Hi Benjamin,

    Thank you for a very useful introduction to Behat. I have copied the steps locally and I have the Calculator example running well.

    I was hoping to extend it a little and add further tests but If I add a new function to features/bootstrap/FeatureContext.php, I get “TODO: write pending definition” when I run bin/behat.

    Can you let me know if there is an additional step I must take after adding a new function to features/bootstrap/FeatureContext.php ?

    Thank you.

  • Sorry, you can ignore my previous comment. I created a new function successfully but I didn’t give it anything to do with the $arg, so it threw an exception. My bad.

  • Pingback: BDD & TDD | Tech Ramblings()

  • Pawan

    I followed ever instructions step by step but my FeatureContext.php generated quite different because it implements SnippetAcceptingContext rather then extending BehatContext. Then I changed it to look alike in the example and tried to include the necessary classes following the instruction os the Behat docs http://docs.behat.org/quick_intro.html

    But I get the error “Fatal error: Class ‘Behat\Behat\Context\BehatContext’ not found in /my path/behat/features/bootstrap/FeatureContext.php on line 16′.

    I am sorry if my comment is stupid but any suggestion would be of great help.

    Thanks in Advance.

  • Hi Pawan, I think you are using the 3.0 branch of Behat and this post uses the <3.0 version. I will add a disclaimer at the beginning of the post…

    Let me know if you still have any troubles.

  • Sai Satish

    I’m trying to write test cases using Behat.

    However, I came across a scenario where I need to remove text from the text field.

    Could you please assist me with Script that can perform this functionality

  • Hi Sai, sorry for the delay…

    I think setting the value of the field to “” with mink should do the trick. But why you want to do this?