No More Seat Costs: Semaphore Plans Just Got Better!

    4 Aug 2015 · Software Engineering

    Getting Started with BDD in Laravel

    13 min read
    Contents

    Introduction

    For many developers BDD is a complicated subject, and getting started with it the right way often does not come easy – especially when you need to implement it into an existing framework. This tutorial aims to help you get a BDD-powered Laravel project up and running in very little time, introducing you to the basic concepts and workflow you’ll need to proceed on your own. We’ll be installing and using Behat and PhpSpec.

    In the tutorial, we assume you’re working on a Unix system, and have basic theoretical knowledge of what BDD is about, but little to no practical experience.

    We’ll also assume that us saying “Run the command” implies the command should be run in the terminal of the operating system.

    Prerequisites

    Optionally, if you intend to build what we set up here into a proper application, add in:

    • A database (MySQL)
    • Caching layers (Redis, Memcached, etc.)

    Creating a New Laravel App

    To create a new Laravel application, run the following command:

    composer create-project laravel/laravel bdd-setup

    The sample application has now been created, and should greet you with “Laravel 5” when you visit the root of the app.

    Laravel Greeting Screen

    Setting Up Behat

    Several packages are required in order to make Behat play nice with Laravel. Let’s install them all into our application’s development environment (with the --dev flag) and explain each.

    composer require behat/behat behat/mink behat/mink-extension laracasts/behat-laravel-extension --dev
    sudo ln -s /home/vagrant/Code/bdd-setup/vendor/bin/behat /usr/local/bin/behat
    

    behat/behat is the main package for Behat. The behat/mink package is used to emulate a browser, so we can have the test suite check our URLs and their output. behat/mink-extension is the glue for Mink and Behat, and the last package, behat-laravel-extension is Jeffrey Way‘s own implementation of Behat bindings, specifically made for Laravel.

    The last sudo ln -s line is optional, and adds Behat’s executable to a location in the $PATH, so the behat command can be executed without the vendor/bin prefix from our project’s root folder. In other words, behat --init instead of vendor/bin/behat --init.

    Finally, we initialize a Behat project:

    behat --init

    This has created a new folder named features in our project’s directory:

    Features Bootstrap Folder

    Context and Configuration

    Behat uses definitions from the auto-generated FeatureContext class to understand what we’re testing, using phrasing like “Given I’m on the URL this and that…”.

    To get some typical browser-related definitions, we need to make sure the FeatureContext class extends the MinkContext class which contains them.

    Thus, we will alter the source code of FeatureContext.php from:

    class FeatureContext implements Context, SnippetAcceptingContext

    to:

    class FeatureContext extends Behat\MinkExtension\Context\MinkContext implements Context, SnippetAcceptingContext

    With this change, we made FeatureContext inherit the definitions within MinkContext.

    The behat -dl command is used to list out all existing definitions. It will now output something similar to the following:

    default | Given /^(?:|I )am on (?:|the )homepage$/
    default |  When /^(?:|I )go to (?:|the )homepage$/
    default | Given /^(?:|I )am on "(?P<page>[^"]+)"$/
    default |  When /^(?:|I )go to "(?P<page>[^"]+)"$/
    default |  When /^(?:|I )reload the page$/
    ...

    Next, we need to set up the Laravel-specific package. As per Laracasts instructions, this is done by adding a behat.yml file to the project root:

    default:
        extensions:
            Laracasts\Behat:
                # env_path: .env.behat
            Behat\MinkExtension:
                default_session: laravel
                base_url: http://localhost:8888
                laravel: ~

    The .env.behat file referenced above contains environment variables specific to the Behat testing session. This file does not exist by default, so we can create it by copying the already included .env.example one:

    cp .env.example .env.behat

    Note: Due to varying installation procedures between different Laravel versions, you might have to add a custom made key into APP_KEY in both config/app.php and .env, as well as .env.behat. Keeping it at under 32 characters (default is “Some random string”) will throw errors.

    ##Writing Features

    Features are what we test for with Behat. We write them out as human-readable stories, and expect the test suite to not only understand them, but also to make sure they work.

    One such feature can be checking for whether or not we see “Laravel 5” when we visit the home page. To write this feature, we need to create a hometest.feature file in the features folder and give it the following contents:

    Feature:
        In order to prove that Behat works as intended
        We want to test the home page for a phrase

    Every feature begins with such a description. This is for humans only – the test suite is not intended to understand this. They are followed by the Scenarios

    • the specific, computer-readable steps the suite should follow.
    Feature:
        In order to prove that Behat works as intended
        We want to test the home page for a phrase
    
        Scenario: Root Test
            When I am on the homepage
            Then I should see "Laravel 5"

    Every scenario starts with the word “Scenario”, indented to the level of the Feature’s description. Every scenario should also have a name.

    Immediately beneath it, another indentation level in, the scenario will have specific instructions for Behat to follow. These instructions are parsed from definitions we defined in the FeatureContext class. In our case, we defined them by extending MinkContext.

    When I am on the homepage is a specific definition in MinkContext which states:

        /**
         * Opens homepage.
         *
         * @Given /^(?:|I )am on (?:|the )homepage$/
         * @When /^(?:|I )go to (?:|the )homepage$/
         */
        public function iAmOnHomepage()
        {
            $this->visitPath('/');
        }

    In other words, these two phrases will trigger this: Given I am on the homepage and When I am on the homepage. The function will simulate a visit to the root URL: /.

    The next definition, Then I should see "Laravel 5" calls on:

        /**
         * Checks, that page contains specified text.
         *
         * @Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
         */
        public function assertPageContainsText($text)
        {
            $this->assertSession()->pageTextContains($this->fixStepArgument($text));
        }

    The function grabs all the text from the rendered page, and checks if our string is a substring of it.

    Before testing for this, however, we need to boot up a local PHP server, just so Mink can actually access the URLs we ask it to access.

    php -S localhost:8888 -t public

    The above command launches a server (-S), on the url localhost, listening on the port 8888 in the target directory public.

    Finally, we can test the feature:

    > behat
    
    Feature:
      In order to prove that Behat works as intended
      We want to test the home page for a phrase
    
      Scenario: Root Test             # features/hometest.feature:5
        When I am on the homepage     # FeatureContext::iAmOnHomepage()
        Then I should see "Laravel 5" # FeatureContext::assertPageContainsText()
    
    1 scenario (1 passed)
    2 steps (2 passed)
    0m0.64s (22.13Mb)
    

    The basics of Behat are now in place. We’ll work on some in-depth integrations in a future post.

    Note: By using the behat-laravel-extension package, we made sure all Laravel functionality is instantly available in the FeatureContext. Getting to the main $app object is now as simple as app(), getting a configuration variable is just a config("somevar") away. These bindings are all automatically available and ready to be used.

    Using PHPUnit’s Assertions

    Behat doesn’t have assertions per-se. So, you may want to use PHPUnit’s assertions. Seeing as PHPUnit comes bundled with new Laravel apps, it’s already available, and all one needs to do to access the assertions is import the class in the FeatureContext class, as follows:

    use PHPUnit_Framework_Assert as PHPUnit;
    

    You will then have access to assertions:

    Autocomplete Dialog for Assertions in the Imported PHPUnit Class

    You can see a full list of available assertions here.

    PhpSpec

    PhpSpec is increasingly being used as a common replacement for PHPUnit in people’s arsenals. Laravel does come with PHPUnit, but that doesn’t mean there’s no room for replacing or supplementing it with PhpSpec.

    The most noticeable difference between PhpSpec and PHPUnit is the syntax – PhpSpec is much more readable and human-friendly, thus fitting in nicely with the whole concept of BDD and Behat. The tests don’t have to begin with the word test, and the methods are all phrased as sentences, as they represent actions we intend to do, or properties we want objects to have. Even the docs confirm this:

    There is no real difference between SpecBDD and TDD. The value of using an xSpec tool instead of a regular xUnit tool for TDD is the language.

    In addition, PhpSpec helps with the scaffolding of tests and classes, and with mocking. We’ll cover how this is done in another, more in-depth tutorial, but for now let’s install and set it up, and then go through some basics.

    Let’s install PhpSpec:

    composer require phpspec/phpspec --dev

    Again, we can add the installed executable to our path, so it’s runnable without the vendor/bin prefix. Either execute the command below to do so (modify the paths to match yours), or just add the whole vendor/bin folder to your path – whichever way you prefer.

    sudo ln -s /home/vagrant/Code/bdd-setup/vendor/bin/phpspec /usr/local/bin/phpspec

    PhpSpec is more or less ready to roll out of the box, so we just need one more minor edit. In phpspec.yml in the root of our project folder, under all the lines in there, we will add:

            spec_path: tests

    This tells PhpSpec where to put our spec files. Feel free to change this as you wish.

    Writing Specs

    Specs are classes containing tests, much like test classes in PHPUnit. To create a new spec for a class, we use the desc command (for describe). Let’s imagine we’re making a calculator class we intend to build into Laravel as a service. In version 1, a calculator should at the very least be able to sum two numbers. Let’s build this version 1.

    phpspec desc bddsetup\\Calculator

    Note that bddsetup is this tutorial’s demo namespace, and you should change it to yours if you picked a different one.

    This has created a specification file in tests/spec/CalculatorSpec.php, containing the following:

    <?php
    
    namespace spec\bddsetup;
    
    use PhpSpec\ObjectBehavior;
    use Prophecy\Argument;
    
    class CalculatorSpec extends ObjectBehavior
    {
        function it_is_initializable()
        {
            $this->shouldHaveType('bddsetup\Calculator');
        }
    }

    Note: The $this keyword refers to the instance of the class being tested (Calculator), and not the test class itself!

    If we run phpspec now, it will ask us for permission to create the missing Calculator class for us. Let’s allow it.

    bddsetup/Calculator
      10  - it is initializable
          class bddsetup\Calculator does not exist.
    
                                          100%                                       1
    1 specs
    1 example (1 broken)
    105ms
    
    
      Do you want me to create bddsetup\Calculator for you?
                                                                             [Y/n]
    Y
    Class bddsetup\Calculator created in /home/vagrant/Code/bdd-setup/app/Calculator.php.
    
                                          100%                                       1
    1 specs
    1 example (1 passed)
    135ms

    This automatically passes the test because the it_is_initializable test succeeds – the class exists now, after all.

    Let’s use Behat and PhpSpec in tandem to create a sum method, now.

    The Duet

    In true BDD fashion, we envision a feature first, write it out, and then test for its existence. Let’s create a new feature file at features/calc.feature:

    Feature:
      In order to make sure the calculator works
      As a developer
      I need to get the correct output from its functions
    
      Scenario: Summing
        Given the method "sum" receives the numbers 4 and 7
        Then the calculated value should be 11

    The two definitions in the Summing scenario do not exist. We need to add them into the FeatureContext, so that Behat can understand them. An easy way to generate empty snippets for us to fill out is by using the --append-snippets command.

    behat --append-snippets

    The FeatureContext class should now have two additional methods:

        /**
         * @Given the method :arg1 receives the numbers :arg2 and :arg3
         */
        public function theMethodReceivesTheNumbersAnd($arg1, $arg2, $arg3)
        {
            throw new PendingException();
        }
    
        /**
         * @Then the calculated value should be :arg1
         */
        public function theCalculatedValueShouldBe($arg1)
        {
            throw new PendingException();
        }

    Behat automatically extracted the arguments it recognized. This means the methods (and, by extension, the definitions) are flexible – we can alter the parameters as we see fit. Let’s fill out those stubs now.

        /**
         * @Given the method :arg1 receives the numbers :arg2 and :arg3
         */
        public function theMethodReceivesTheNumbersAnd($arg1, $arg2, $arg3)
        {
            $this->calculator = new Calculator();
            $this->calculator->$arg1($arg2, $arg3);
        }
    
        /**
         * @Then the calculated value should be :arg1
         */
        public function theCalculatedValueShouldBe($arg1)
        {
            PHPUnit::assertEquals($arg1, $this->calculator->result());
        }

    You can see here we’re using the PHPUnit assertions from before, despite having both PhpSpec and Behat at our disposal.

    If we run Behat now, we should get:

    [Symfony\Component\Debug\Exception\FatalErrorException]  
      Call to undefined method bddsetup\Calculator::sum()

    That’s normal. After all, we didn’t implement it. Let’s have PhpSpec help us out with that. Add a new method into the CalculatorSpec:

        function it_should_sum()
        {
            $this->sum(4, 7);
            $this->result()->shouldBe(11);
        }

    When we run it, PhpSpec will ask for permission to stub out the sum and result methods:

    > phpspec run
    
    bddsetup/Calculator
      15  - it should sum
          method bddsetup\Calculator::sum not found.
    
                      50%                                     50%                    2
    1 specs
    2 examples (1 passed, 1 broken)
    153ms
    
    
      Do you want me to create bddsetup\Calculator::sum() for you?
                                                                             [Y/n]
    Y
      Method bddsetup\Calculator::sum() has been created.
    
    bddsetup/Calculator
      15  - it should sum
          method bddsetup\Calculator::result not found.
    
                      50%                                     50%                    2
    1 specs
    2 examples (1 passed, 1 broken)
    136ms
    
    
      Do you want me to create bddsetup\Calculator::result() for you?
                                                                             [Y/n]
    Y
      Method bddsetup\Calculator::result() has been created.
    
    bddsetup/Calculator
      15  - it should sum
          expected [integer:11], but got null.
    
                      50%                                     50%                    2
    1 specs
    2 examples (1 passed, 1 failed)
    144ms

    At the same time, the run fails because the methods don’t do what they’re expected to do. This is perfectly fine. Let’s edit the Calculator class and implement them completely:

    <?php
    
    namespace bddsetup;
    
    class Calculator
    {
        protected $result = 0;
    
        public function sum($argument1, $argument2)
        {
            $this->result = (int)$argument1 + (int)$argument2;
        }
    
        public function result()
        {
            return $this->result;
        }
    }

    If we now run Behat with behat and PhpSpec with phpspec run, we should get all green results – all tests should pass.

    It is now much easier to imagine extending the class quickly and effectively:

    • Omitting the second argument could add the one that was passed into the result from a previous operation,
    • The sum method could return the Calculator instance to enable chaining, playing nicely into the point above, etc.

    Conclusion

    With powerful BDD tools such as Behat and PhpSpec in place, writing out stories and testing your classes for future upgrades becomes a breeze, rather than a tedious night of writing mocks.

    This tutorial showed you how to get started with BDD tools in a fresh Laravel application. What was shown in this post is just enough to whet your appetite. Future posts will go into more detail and some use-case specific implementations.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    Bruno is the editor of SitePoint’s PHP channel and developer evangelist for Diffbot. He’s a treadmill desker and an active (board)gamer who sometimes blogs.