Why

It has been known lately that BDD is a favourable testing approach especially when it comes to building APIs, be it a public for a variety of client implementations (mobile apps, websites or 3rd party consumers) or using microservices, in both cases the is the backbone that your flow relies on so the more robust it is the more reliable it will be which brings us to testing.

If you are asking why Behat? I am sure you’ve looked that up multiple times and found numerous readings about it but I will have to credit my decision to Robert C. Martin or more commonly known as Uncle Bob who said in The Truth about BDD:

It connects the human concept of cause and effect, to the software concept of input/process/output.

In addition to comparing it to a way to describe the finite state machine which is how I often find myself understanding software.

What

We will be building a sample API using Behat 3.x and Laravel 5.1.

How (Code Please)

Installation

Let’s start with a fresh Laravel 5.1 installation

composer create-project laravel/laravel behat-l5-apis ~5.1

Next we will add Behat to our development requirements

composer require --dev behat/behat 3.0

After this step we should have vendor/bin/behat there and ready for our commads.

It is handy to add ./vendor/bin to your PATH so that you can call commands without mentioning their path.

export PATH=$PATH:./vendor/bin

Remember to reload your terminal session for the changes to take effect.

If you’re not familiar with the Gherkin syntax (it’s the English your write that magically translates into machine language) it’s fairly simple and no, nothing is magical about it and you will realise that as you read along.

It’s about Features and Scenarios. A feature is obviously the feature we’re testing and it is composed of scenarios, think of them as the possible ways the user could be interacting with this feature. Here’s an example:

Feature: Building Cars
    In order to facilitate human transportation
    As a manufacturer
    I need to build them fancy cars
    Scenario: Build a regular 4-seater vehicle
        Given I want to build a car named "Reek"
        And I have 4 doors
        And I have 4 wheels
        And the colour is "blue"
        And the engine has the power of 600 horses
        When I build the car
        Then I should get:
        """
        {
            "status": "success",
            "time": "2 days"
        }
        """

For more details about the language visit Writing Features — Gherkin Language.

Configuration

We need to tell Behat how to locate our tests, the configuration is in YAML.

Create a file called behat.yml at the root of the project with this content:

default:
    suites:
        api:
            paths: [tests/Api/features]
            contexts: [\Tests\Api\ApiFeatureContext]

What this basically says is: “Behat, I have a group of features for the API that I’d like to have them loaded from tests/Api/features and use ApiFeatureContextas my context class”. What is a context class you ask? you’ll know in a bit.

Now we go ahead and create the latter.

tests/Api/features <<< This is our features directory
tests/Api/ApiFeatureContext.php <<< This is our context class

The context class ApiFeatureContext is the class that will have the definitions of sentences such as “Given I bla bla bla…” and to do that the content of that file should be:

<?php
namespace App\Tests\Api;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
class ApiFeatureContext implements Context, SnippetAcceptingContext
{
}

Before we test the waters we need to ask composer to autoload our test classes using PSR-4. In composer.json at the autoload section we modify psr-4 with the following:

“psr-4”: {
    “App\\”: “app/”,
    “App\\Tests\\”: “tests/”
}

Reload your classes with

composer dump-autoload

Now we’re good to go, if you run behat you should get this:

> behat
No scenarios
No steps
0m0.00s (.29Mb)

Hurray!

Setup

As you may know Laravel ships with a handy of assertions and crawler methods that are extremely useful for testing such as:

$this->visit(‘/’)
    ->see('Laravel 5');
$this->post('/user', ['name' => 'Sally'])
    ->seeJson([
        'created' => true,
    ]);

I hear you “we want those too!” so let’s have them available for us in our Context class before diving into the implementation.

We will create a “base” feature context calling it LaravelFeatureContext which can be extended if need be.

In tests/LaravelFeatureContext.php

<?php
namespace App\Tests;
use TestCase;
use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
class LaravelFeatureContext extends TestCase implements Context, SnippetAcceptingContext
{
}

Now that our beloved LaraveFeatureContext class is implementing Context and SnippetAcceptingContext we won’t have to in our child context classes so our ApiFeatureContext will look like this

<?php
namespace App\Tests\Api;
use App\Tests\LaravelFeatureContext;
class ApiFeatureContext extends LaravelFeatureContext
{
}

Implementation

Let’s start writing our first feature test. We will begin using our previous feature example.

In tests/Api/features/building-cars.feature

Feature: Building Cars
    In order to facilitate human transportation
    As a manufacturer
    I need to build them fancy cars
Scenario: Build a regular 4-seater vehicle
        Given I want to build a car named "Reek"
        And I have 4 doors
        And I have 4 wheels
        And the colour is "blue"
        And the engine has the power of 600 horses
        When I build the car
        Then I should get:
        """
        {
            "status": "success",
            "time": "2 days"
        }
        """

Now we need to ask Behat to kindly generate the required context methods in our context class by running:

behat --dry-run --append-snippets

The output should look similar to this:

Now if we take a look at ApiFeatureContext, SURPRISE! The methods that implement the definitions of the Gherkin sentences we just wrote are there for us to implement.

If we take a look at the first method (as a personal preference I like to rename the arguments from $arg1.. to something more meaningful) it is where we will start building our car:

use App\Builders\CarBuilder;
...
protected $carBuilder;
/**
 * @Given I want to build a car named :name
 */
public function iWantToBuildACarNamed($name)
{
    $this->carBuilder = new CarBuilder();
    $this->carBuilder->setName($name);
}

Now we need our CarBuilder class, we will be evolving this class as we move on with the tests.

In app/Builders/CarBuilder.php

<?php
namespace App\Builders;
class CarBuilder
{
    protected $name;
    public function setName($name)
    {
        $this->name = $name;
    }
}

Now if we run behat again our first step of the scenario is now green.

Now we fill the rest of the definitions and implement the corresponding builder methods until we get to build and verify (assert) the response:

/**
 * @When I build the car
 */
public function iBuildTheCar()
{
    $this->buildResponse = $this->carBuilder->make();
}

Now we need to assert that the build response was as expected, to do that we will need to use PHPUnit’s assertions, PHPUnit is already installed for us by Laravel so all we have to do is import the namespace:

use PHPUnit_Framework_Assert as PHPUnit;

Now we can use it as PHPUnit::assertEquals(…)

Let’s assert our build status was as expected

/**
 * @Then I should get:
 */
public function iShouldGet(PyStringNode $string)
{
    $expected = json_decode($string, true);
    PHPUnit::assertEquals($expected, $this->buildResponse);
}

That PyStringNode came out of nowhere so we need to import it from Behat’s namespace

use Behat\Gherkin\Node\PyStringNode;

And CarBuilder::make() is just a function lying to us about the build for now:

public function make()
{
    return [
        ‘status’ => ‘success’,
        ‘time’ => ‘2 days’,
    ];
}

If all the methods were implemented and working our tests should all be green as such:

Wrap Up

Finally, this example’s purpose was to give you an idea about how you could setup Behat and Laravel although we haven’t harnessed the powers that Laravel provides, which is what we’ll do in the next part (part 2).

No tags for this post.

LEAVE A REPLY

Please enter your comment!
Please enter your name here