1. Code
  2. PHP
  3. Laravel

Laravel, BDD and You: Let’s Get Started

Scroll to top
This post is part of a series called Laravel, BDD And You.
Laravel, BDD and You: The First Feature

Welcome to this series about developing Laravel applications using a behavior-driven development (BDD) approach. Full stack BDD can seem complicated and intimidating. There are just as many ways of doing it as there are developers. 

In this series, I will walk you through my approach of using Behat and PhpSpec to design a Laravel application from scratch.

There are many resources on BDD in general, but Laravel specific material is hard to find. Therefore, in this series, we will focus more on the Laravel related aspects and less on the general stuff that you can read about many other places.

Describing Behavior

When describing behavior, which is also known as writing stories and specs, we will be using an outside-in approach. This means that every time we build a new feature, we will start by writing the overall user story. This is normally from the clients or stakeholders perspective. 

What are we expecting to happen when we do this? 

We are not allowed to write any code until we have a failing, red step for example, unless we are refactoring existing code. 

Sometimes, it will be necessary to iteratively solve a small part of a story or feature (two words that I use interchangeably) on which we are working. This will often mean writing specs with PhpSpec. Sometimes it will take many iterations on a integration or unit level before the whole story (on an acceptance level) is passing. This all sounds very complicated but it's really not. I am a big believer of learning by doing, so I think that everything will make more sense once we start writing some actual code.

We will be writing stories and specs on four different levels:

1. Acceptance

Most of the time, our functional suite will serve as our acceptance layer. The way we will be describing our features in our functional suite will be very similar to how we would write acceptance stories (using an automated browser framework) and would as such create a lot of duplication. 

As long as the stories describe the behavior from the client's point of view, they serve as acceptance stories. We will use the Symfony DomCrawler to test the output of our application. Later in the series, we might find that we need to test through an actual browser that can run JavaScript as well. Testing through the browser adds some new concerns, since we need to make sure that we load hour test environment when the suite is run.

2. Functional

In our functional suite, we will have access to the Laravel application, which is very convenient. First of all, it makes it easy to differentiate between environments. Second of all, not going through a browser makes our test suite a lot faster. Whenever we want to implement a new feature, we will write a story in our functional suite using Behat.

3. Integration

Our integration suite will test the behavior of the core part of our application that do not neccessarily need to have access to Laravel. The integration suite will normally be a mixture of Behat stories and PhpSpec specs.

4. Unit

Our unit tests will be written in PhpSpec and will test isolated small units of the application core. Our entities, value objects etc. will all have specs.

The Case

Throughout this series, starting from the next article, we will be building a system for tracking time. We will start by describing the behavior from the outside by writing Behat features. The internal behavior of our application will be described using PhpSpec. 

Together these two tools will help us feel comfortable with the quality of the application we are building. We will primarily write features and specs on three levels: 

  1. Functional
  2. Integration
  3. Unit


In our functional suite, we will crawl the HTTP responses of our application in a headless mode, meaning that we will not go through the browser. This will make it easier to interact with Laravel and make our functional suite serve as our acceptance layer, as well. 

Later on, if we end up having a more complicated UI and might need to test some JavaScript as well, we might add a dedicated acceptance suite. This series is still work-in-progress, so feel free to drop your suggestions in the comments section.

Our Setup

Note that for this tutorial, I assume you have a fresh install of Laravel (4.2) up and running. Preferably you are using Laravel Homestead as well, which is what I used when I wrote this code.

Before we get started with any real work, let's make sure we have Behat and PhpSpec up and running. First though, I like to do a little bit of cleaning whenever I start a new laravel project and delete the stuff I do not need:

1
git rm -r app/tests/ phpunit.xml CONTRIBUTING.md

If you delete these files, make sure to update your composer.json file accordingly:

1
"autoload": {
2
    "classmap": [
3
        "app/commands",
4
        "app/controllers",
5
        "app/models",
6
        "app/database/migrations",
7
        "app/database/seeds"
8
    ]
9
},

And, of course:

1
$ composer dump-autoload

Now we are ready to pull in the BDD tools we need. Just add a require-dev section to your composer.json:

1
"require": {
2
    "laravel/framework": "4.2.*"
3
},
4
"require-dev": {
5
    "behat/behat": "~3.0",
6
    "phpspec/phpspec": "~2.0",
7
    "phpunit/phpunit": "~4.1"
8
},

"Why are we pulling in PHPUnit?" you might be thinking? We are not going to write good ol' PHPUnit test cases in this series, but the assertions are a handy tool together with Behat. We will see that later in this article when we write our first feature.

Remember to update you dependencies after modifying composer.json:

1
$ composer update --dev

We are almost done installing and setting up stuff. PhpSpec works out of the box:

1
$ vendor/bin/phpspec run
2
3
0 specs
4
0 examples 
5
0ms

But Behat needs to do a quick run with the --init option in order to set everything up:

1
$ vendor/bin/behat --init
2
3
+d features - place your *.feature files here
4
+d features/bootstrap - place your context classes here
5
+f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here
6
7
$ vendor/bin/behat
8
9
No scenarios
10
No steps
11
0m0.14s (12.18Mb)

The first command created a shiny new FeatureContext class, where we can write the step definitions needed for our features:

1
<?php
2
3
use Behat\Behat\Context\SnippetAcceptingContext;
4
use Behat\Gherkin\Node\PyStringNode;
5
use Behat\Gherkin\Node\TableNode;
6
7
/**

8
 * Behat context class.

9
 */
10
class FeatureContext implements SnippetAcceptingContext
11
{
12
    /**

13
     * Initializes context.

14
     *

15
     * Every scenario gets its own context object.

16
     * You can also pass arbitrary arguments to the context constructor through behat.yml.

17
     */
18
    public function __construct()
19
    {
20
    }
21
}

Writing Our First Feature

Our first feature will be very simple: We will simply make sure that our new Laravel install greets us with a "You have arrived." on the homepage. I put in a rather silly Given step as well, Given I am logged in, which only serves to show how easy interacting with Laravel in our features is.

Technically, I would categorize this type of feature as a functional test, since it interacts with the framework, but it also serves as an acceptance test, since we would not see any different results from running a similar test through a browser testing tool. For now we will stick with our functional test suite.

Go ahead and create a welcome.feature file and put it in features/functional:

1
# features/functional/welcome.feature
2
3
Feature: Welcoming developer
4
    As a Laravel developer
5
    In order to proberly begin a new project
6
    I need to be greeted upon arrival
7
8
    Scenario: Greeting developer on homepage
9
        Given I am logged in
10
        When I visit "/"
11
        Then I should see "You have arrived."

By putting the functional features in a functional directory, it will be easier for us to manage our suites later on. We do not want integration type features that does not require Laravel to have to wait for the slow functional suite. 

I like to keep things nice and clean, so I believe we should have a dedicated feature context for our functional suite that can give us access to Laravel. You can just go ahead and copy the existing FeatureContext file and change the class name to LaravelFeatureContext. For this to work, we also need a behat.yml configuration file. 

Create one in the root directory of you project and add the following:

1
default:
2
    suites:
3
        functional:
4
            paths: [ %paths.base%/features/functional ]
5
            contexts: [ LaravelFeatureContext ]

I think the YAML here is pretty self explanatory. Our functional suite will look for features in the functional directory and run them through the LaravelFeatureContext.

If we try to run Behat at this point, it will tell us to implement the necessary step definitions. We can have Behat add the empty scaffold methods to the LaravelFeatureContext with the following command:

1
$ vendor/bin/behat --dry-run --append-snippets
2
$ vendor/bin/behat
3
4
Feature: Welcoming developer
5
    As a Laravel developer
6
    In order to proberly begin a new project
7
    I need to be greeted upon arival
8
9
  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6

10
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()

11
      TODO: write pending definition
12
    When I visit "/"                       # LaravelFeatureContext::iVisit()

13
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

14
15
1 scenario (1 pending)
16
3 steps (1 pending, 2 skipped)
17
0m0.28s (12.53Mb)

And now, as you can see from the output, we are ready to start implement the first of our steps: Given I am logged in.

The PHPUnit test case that ships with Laravel allows us to do stuff like $this->be($user), which logs in a given user. Ultimately, we want to be able to interact with Laravel as if we were using PHPUnit, so let's go ahead and write the step definition code "we wish we had":

1
/**

2
 * @Given I am logged in

3
 */
4
public function iAmLoggedIn()
5
{
6
    $user = new User;
7
8
    $this->be($user);
9
}

This will not work of course, since Behat have no idea about Laravel specific stuff, but I will show you in just a second how easy it is to get Behat and Laravel to play nicely together.

If you take a look in the Laravel source and find the Illuminate\Foundation\Testing\TestCase class, which is the class that the default test case extends from, you will see that starting from Laravel 4.2, everything has been moved to a trait. The ApplicationTrait is now responsible for booting an Application instance, setting up an HTTP client and give us a few helper methods, such as be()

This is pretty cool, mainly because it means that we can just pull it in to our Behat contexts with almost no setup required. We also have access to the AssertionsTrait, but this is still tied to PHPUnit.

When we pull in the trait, we need to do two things. We need to have a setUp() method, like the one in theIlluminate\Foundation\Testing\TestCase class, and we need a createApplication() method, like the one in the default Laravel test case. Actually we can just copy those two methods and use them directly. 

There is only one thing to notice: In PHPUnit, the method setUp() will automatically be called before each test. In order to achieve the same in Behat, we can use the @BeforeScenario annotation.

Add the following to your LaravelFeatureContext:

1
use Illuminate\Foundation\Testing\ApplicationTrait;
2
3
/**

4
 * Behat context class.

5
 */
6
class LaravelFeatureContext implements SnippetAcceptingContext
7
{
8
    /**

9
     * Responsible for providing a Laravel app instance.

10
     */
11
    use ApplicationTrait;
12
13
    /**

14
     * Initializes context.

15
     *

16
     * Every scenario gets its own context object.

17
     * You can also pass arbitrary arguments to the context constructor through behat.yml.

18
     */
19
    public function __construct()
20
    {
21
    }
22
23
    /**

24
     * @BeforeScenario

25
     */
26
    public function setUp()
27
    {
28
        if ( ! $this->app)
29
        {
30
            $this->refreshApplication();
31
        }
32
    }
33
34
    /**

35
     * Creates the application.

36
     *

37
     * @return \Symfony\Component\HttpKernel\HttpKernelInterface

38
     */
39
    public function createApplication()
40
    {
41
        $unitTesting = true;
42
43
        $testEnvironment = 'testing';
44
45
        return require __DIR__.'/../../bootstrap/start.php';
46
    }

Pretty easy, and look what we get when we run Behat:

1
$ vendor/bin/behat
2
3
Feature: Welcoming developer
4
    As a Laravel developer
5
    In order to proberly begin a new project
6
    I need to be greeted upon arival
7
8
  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6

9
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()

10
    When I visit "/"                       # LaravelFeatureContext::iVisit()

11
      TODO: write pending definition
12
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

13
14
1 scenario (1 pending)
15
3 steps (1 passed, 1 pending, 1 skipped)
16
0m0.73s (17.92Mb)

A green first step, which means that our setup is working!

Next up, we can implement the When I visit step. This one is super easy, and we can simply use the call()method that the ApplicationTrait provides. One line of code will get us there:

1
/**

2
 * @When I visit :uri

3
 */
4
public function iVisit($uri)
5
{
6
    $this->call('GET', $uri);
7
}

The last step, Then I should see, takes a little more and we need to pull in two dependencies. We will need PHPUnit for the assertion and we will need the Symfony DomCrawler to search for the "You have arrived." text.

We can implement it like this:

1
use PHPUnit_Framework_Assert as PHPUnit;
2
use Symfony\Component\DomCrawler\Crawler;
3
4
...
5
6
/**

7
 * @Then I should see :text

8
 */
9
public function iShouldSee($text)
10
{
11
    $crawler = new Crawler($this->client->getResponse()->getContent());
12
13
    PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']"));
14
}

This is pretty much the same code as you would write if you were using PHPUnit. The filterXpath() part is a little confusing and we will not worry about it now, since it is a little out of the scope of this article. Just trust me that it works.

Running Behat one final time is good news:

1
$ vendor/bin/behat
2
Feature: Welcoming developer
3
    As a Laravel developer
4
    In order to proberly begin a new project
5
    I need to be greeted upon arival
6
7
  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6

8
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()

9
    When I visit "/"                       # LaravelFeatureContext::iVisit()

10
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

11
12
1 scenario (1 passed)
13
3 steps (3 passed)
14
0m0.82s (19.46Mb)

The feature is working as expected and the developer is greeted upon arrival.

Conclusion

The complete LaravelFeatureContext should now look similar to this:

1
<?php
2
3
use Behat\Behat\Context\SnippetAcceptingContext;
4
use Behat\Gherkin\Node\PyStringNode;
5
use Behat\Gherkin\Node\TableNode;
6
7
use PHPUnit_Framework_Assert as PHPUnit;
8
use Symfony\Component\DomCrawler\Crawler;
9
10
use Illuminate\Foundation\Testing\ApplicationTrait;
11
12
/**

13
 * Behat context class.

14
 */
15
class LaravelFeatureContext implements SnippetAcceptingContext
16
{
17
    /**

18
     * Responsible for providing a Laravel app instance.

19
     */
20
    use ApplicationTrait;
21
22
    /**

23
     * Initializes context.

24
     *

25
     * Every scenario gets its own context object.

26
     * You can also pass arbitrary arguments to the context constructor through behat.yml.

27
     */
28
    public function __construct()
29
    {
30
    }
31
32
    /**

33
     * @BeforeScenario

34
     */
35
    public function setUp()
36
    {
37
        if ( ! $this->app)
38
        {
39
            $this->refreshApplication();
40
        }
41
    }
42
43
    /**

44
     * Creates the application.

45
     *

46
     * @return \Symfony\Component\HttpKernel\HttpKernelInterface

47
     */
48
    public function createApplication()
49
    {
50
        $unitTesting = true;
51
52
        $testEnvironment = 'testing';
53
54
        return require __DIR__.'/../../bootstrap/start.php';
55
    }
56
57
    /**

58
     * @Given I am logged in

59
     */
60
    public function iAmLoggedIn()
61
    {
62
        $user = new User;
63
64
        $this->be($user);
65
    }
66
67
    /**

68
     * @When I visit :uri

69
     */
70
    public function iVisit($uri)
71
    {
72
        $this->call('GET', $uri);
73
    }
74
75
    /**

76
     * @Then I should see :text

77
     */
78
    public function iShouldSee($text)
79
    {
80
        $crawler = new Crawler($this->client->getResponse()->getContent());
81
82
        PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']"));
83
    }
84
}

We now have a really nice foundation to build upon as we continue developing our new Laravel application using BDD. I hope I have proven to you how easy it is to get Laravel and Behat to play nicely together. 

We have touched on a lot of different topics in this first article. No need to worry, we will take a more in-depth look at everything as the series continues. If you have any questions or suggestions, please leave a comment.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.