Talking English To Your Computer


In this video we are going to get started writing the code that turns the English language Behat feature into something that the computer - or more specifically, Behat itself, can run during our test suite execution.

Behat converts the text in your feature documents into code by way of regular expressions. Now, don't worry if your regex knowledge is only so-so. All we need to do is write the feature in English (or your native language), and then run Behat, and Behat will create snippets of code for you containing the regex, and the associated function stub.

I prefer to copy / paste these stubs into my Context files so I control exactly what's happening, but you can get Behat to append any new snippets / stubs directly in to your Context files for you if they don't yet exist. Find out more about that here, if you wish to use that feature.

Symfony 3 / Behat 3 Integration Update

Since I recorded the video on Installing Behat in Symfony 3, there has been further improvement on support between the two. A huge thank you to the community for this.

Whilst the changes have been made, currently they have not been officially merged into the respective packages / libraries / bundles, so I have taken what's there, and created an interim config / composer.json setup:

{
    "name": "a6_chris/symfony.rest.example.dev",
    "license": "proprietary",
    "type": "project",
    "autoload": {
        "psr-4": {
            "": "src/"
        },
        "classmap": [
            "app/AppKernel.php",
            "app/AppCache.php"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.0.*",
        "doctrine/orm": "^2.5",
        "doctrine/doctrine-bundle": "^1.6",
        "doctrine/doctrine-cache-bundle": "^1.2",
        "symfony/swiftmailer-bundle": "^2.3",
        "symfony/monolog-bundle": "^2.8",
        "sensio/distribution-bundle": "^5.0",
        "sensio/framework-extra-bundle": "^3.0.2",
        "incenteev/composer-parameter-handler": "^2.0",

        "nelmio/cors-bundle": "^1.4",
        "nelmio/api-doc-bundle": "^2.11",
        "friendsofsymfony/rest-bundle": "^1.7",
        "csa/guzzle-bundle": "^2.0",
        "jms/serializer-bundle": "^1.1",
        "oneup/flysystem-bundle": "^1.2",
        "friendsofsymfony/user-bundle": "dev-master",
        "lexik/jwt-authentication-bundle": "dev-master"
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0",
        "symfony/phpunit-bridge": "^2.7",

        "phpspec/phpspec": "^2.4",
        "behat/behat": "dev-master",

        "behat/mink": "^1.7",
        "behat/mink-extension": "^2.1",
        "behat/mink-browserkit-driver": "^1.3",
        "behat/symfony2-extension": "dev-symfony3"
    },
    "minimum-stability": "dev",
    "repositories": [
        {
            "type": "git",
            "url": "https://github.com/codereviewvideos/Symfony2Extension"
        }
    ],
    "scripts": {
        "post-install-cmd": [
            "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
        ],
        "post-update-cmd": [
            "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installRequirementsFile",
            "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::prepareDeploymentTarget"
        ]
    },
    "extra": {
        "symfony-app-dir": "app",
        "symfony-bin-dir": "bin",
        "symfony-var-dir": "var",
        "symfony-web-dir": "web",
        "symfony-tests-dir": "tests",
        "symfony-assets-install": "relative",
        "incenteev-parameters": {
            "file": "app/config/parameters.yml"
        }
    }
}

The important changes from last time are the inclusion of the repositories section, and the four additional behat packages inside require-dev.

As a side note, the repositories section is what allows me to reference behat/symfony2-extension by branch name, rather than by a tag. This is infrequently handy, and if you would like to know more, be sure to check out Lorna Jane's blog post - where I first learned about how to do this.

Our behat.yml File

For whatever reason, I didn't get a behat.yml file created for me when I ran vendor/bin/behat --init. Unless I am losing the last of my marbles, the --init argument has always generated me a behat.yml file for every previous project.

However, not to worry. If you also did not get a behat.yml file generated, then simply go ahead and create an empty file and name it as behat.yml. It should live in your project root - right along side composer.json and composer.lock.

Figuring out what to put in this file is a little tricker. To make life easier, here is mine, so far:

# behat.yml

default:

  suites:
    default:
      type: symfony_bundle
      bundle: AppBundle
      contexts:
        - FeatureContext:
            # doctrine: "@doctrine"

  extensions:
    Behat\Symfony2Extension:
      kernel:
        env: "dev"
        debug: "true"

This is the simplest config needed to get Behat to run in our project.

We will be adding more to this file throughout the series, and even by the end of this video we will have added a little more.

This is largely why in the earlier video we couldn't run Behat. The Symfony 2 extension (a bit badly named now in Symfony 3 era) is what integrates your fully booted and ready to go Symfony kernel directly in to your Behat feature contexts. We can also inject all our Symfony services, just like in our Symfony services.yml file. Awesome.

We have configured one test suite - called default. As you can guess, this will be our default test suite. The type and bundle options are related to the Symfony2Extension.

The main area we will focus on is the contexts section. From here we can configure any and all of the context files we will use throughout our Behat test suite. More on this shortly, but if you are keen to know more, be sure to check out the official Behat docs.

Lastly, you can see that although commented out, the FeatureContext entry has a doctrine entry:

doctrine: "@doctrine"

A few important things to note here:

  1. This is how you would inject services in to your Behat features.
  2. The name of the property is really important - doctrine must match to $doctrine in your FeatureContext constructor (more on this soon)
  3. @doctrine is a standard way of referencing a Symfony service - bin/console debug:container for more
  4. Though not obvious, FeatureContext refers to a file by its namespace, any future contexts we create will have a longer path.

Don't worry if all of this is new to you, we will cover each piece as we go.

Keep Testing Separate

There's a situation that we don't want to get in to if we can avoid it. That is, we don't want to have our test suite run against the same environment we are developing under.

The reason for this is that with each test run, the entire database schema is going to be dropped and recreated. As such, if we were working on a bug in app_dev.php, and the test suite ran against app_dev.php, then should we have some perfectly reproduced bug environment going on in our development environment, then re-ran the tests, all our hard work would be dropped.

It might sound a little crazy, but I've done this to myself too many times in the past to not spend the two minutes it takes to mitigate this problem up front.

In the video we switch to app_acceptance.php for our testing. Why acceptance and not 'test'? Well, Symonfy already comes with a test environment and overriding that would be tricky. Besides, Behat is all about BEHavioural Acceptance Testing, so app_acceptance.php makes sense to me.

If you are unsure how to create a new environment, be sure to read the cookbook article on this very subject. Or just watch the video, it's very easy.

In the video I will show you how to configure the app/config/config_acceptance.yml to allow this environment to run properly during test.

Adding Mink Config

To finish up in this step, we are going to add in the Mink Extension config to allow our Behat features to properly interact with our RESTful API during test runs.

Mink is a browser automation software that will simulate a User interacting with our API.

Note, the change of env under the Symfony2Extension to match the environment we are now using in our MinkExtension config.

# behat.yml

default:

  suites:
    default:
      type: symfony_bundle
      bundle: AppBundle
      contexts:
        - FeatureContext:
            # doctrine: "@doctrine"

  extensions:
    Behat\Symfony2Extension:
      kernel:
        env: "acceptance"
        debug: "true"

    Behat\MinkExtension:
      base_url: "http://symfony-rest-example.dev/app_acceptance.php/"
      sessions:
        defaults:
          symfony2: ~

With our behat.yml file looking like the above, we can go ahead and run Behat for the first time without expecting to see any configuration errors.

vendor/bin/behat

At this stage, not a great deal will happen, other than Behat showing all our steps / scenarios from the User Feature as pending / yellow.

More importantly though, Behat will generate all the first steps of each pending scenario for us, so we can copy and paste these into the relevant Context files.

This is hugely useful to us, and is exactly where we will begin in the next video.

Code For This Course

Get the code for this course.

Episodes