BDD basics with PHPSpec

BDD basics with PHPSpec

BDD is a technique used at story level and spec level. The technique is to first describe the behaviour of an object you are about to write. Next you write just enough code to meet that specification.

PHPSpec.net

Sounds interesting. Let’s give it a shot, shall we?

Setup

We’re going to start off creating a fresh project using the Symfony Skeleton, and installing PHPSpec. Using the symfony binary, creating a new project is very straight forward. You can follow along if you like.

symfony new myproject
cd myproject
composer require phpspec/phpspec

Motorcycles

I like motorcycles, so let’s use that as our subject.

First of all, we’ll need to create the specification to describe our motorcycle class. Luckily, PHPSpec can help us create this file.

vendor/bin/phpspec describe App/Motorcycle

PHPSpec will now generate a specification for the App/Motorcycle class in the tests/spec directory. The generated file will look like this:

<?php

namespace spec\App;

use App\Motorcycle;
use PhpSpec\ObjectBehavior;

class MotorcycleSpec extends ObjectBehavior
{
    function it_is_initializable()
    {
        $this->shouldHaveType(Motorcycle::class);
    }
}

As you can see, this doesn’t describe much. The method it_is_initializable will just check if the Motorcycle class can be initialized. Currently, we haven’t created that class yet - so running PHPSpec should give us an error. We can run the tests using the following command.

vendor/bin/phpspec run

PHPSPec Console Results

As expected, PHPSpec tells us our code is broken. It also suggests creating the class for us, so we’ll accept that offer. Note: You can disable the suggestions by passing the --no-code-generation option when running the command. We’ve now got our base class:

<?php

namespace App;

class Motorcycle
{
}

This also means our test should pass, so let’s try!

PHPSPec Console Results

Great. As you noticed, PHPSpec helped us out quite a bit here. As you’ll see in the rest of this post, PHPSpec can actually generate quite a lot of stuff to help us. Let’s start adding some methods to our Motorcycle class.

First custom behaviour

We’re going to start off with a simple behaviour. For example, we want to make sure our motorcycle is currently running. We can add a fairly simple test for that to our specification.

class MotorcycleSpec extends ObjectBehavior
{
    //...

    function it_is_running(){
        $this->isRunning()->shouldReturn(true);
    }
}

Strange. We’re calling the isRunning() method, but it doesn’t exist in our specification. Surprise! There’s a catch. The $this keyword in the specification doesn’t refer to the spec itself, but to our Motorcycle class. Therefore, PHPSpec will try calling the function on the Motorcycle class.

We’re also chaining the shouldReturn function. This is a PHPSpec Matcher. Matchers in PHPSpec are similar to assertions in PHPunit, and describe how an object should behave. There’s a lot of matchers available, to match your usecase.

We can try running our test again.

PHPSPec Console Results

The failing test is obviously the it_is_running test. All tests green looked way better, so let’s fix it. In our Motorcycle class, we’ll add a method isRunning() that returns true.

class Motorcycle
{
    public function isRunning(): bool
    {
        return true;
    }
}

This immediately fixes our tests, as expected.

PHPSPec Console Results

Slightly more advanced testing (Stubs)

Class methods often require arguments to perform actions. Our motorcycle, for example, could have a function canRide(Rider $rider), that returns whether or not the rider can ride the motorcycle. To test this, we’ll add the following stub:

//...
use App\Rider;
//...
class MotorcycleSpec extends ObjectBehavior
{
    //...

    function it_can_be_ridden_by_a_rider(Rider $rider)
    {
        $rider->hasMotorcycleLicense()->willReturn(true);
        $this->canRide($rider)->shouldReturn(true);
    }
}

PHPSPec Console Results

Our tests are failing, but as you can see, PHPSpec suggests generating an interface App\Rider. Perhaps we’ll want to add different types of people later, so using an interface makes sense. Let’s say no for now, and replace our Rider typehint with PersonInterface. This way we can make sure our naming is nice and clean.

We’ll run the PHPSpec command again, and allow it to generate the interface for us. Okay, so now that we’ve got the interface, will our test pass?

PHPSPec Console Results

Obviously not. Our interface doesn’t even have the hasMotorcycleLicense() method signature. Again, PHPSpec suggests generating it, so let’s agree.

Sweet! We’ve now got our interface ready. Note that in the code snippet below, I did add the bool return type myself.

//...

interface PersonInterface
{
    public function hasMotorcycleLicense(): bool;
}

All we need to do now, is implement the canRide method in the Motorcycle class.

class Motorcycle
{
    //...

    public function canRide(PersonInterface $person): bool
    {
        return $person->hasMotorcycleLicense();
    }
}

When we try running PHPSpec again, we can once again see that all our tests are green!

PHPSPec Console Results

Conclusion

The functionalities I’ve shown here are just the tip of the iceberg. Perhaps I can do a follow-up with some more advanced examples, but for now, this is the end. I hope you’ve enjoyed this read, and hopefully you’ve even learned something along the way. I highly suggest that you check out the PHPSpec Manual if you’re interested in learning more.

Sources

Technologies Used

  • Symfony 5.1
  • PHPSpec 6.2