Testing With PhpSpec
Laravel Tutorials / updated: September 16, 2017

Testing With PhpSpec

PhpSpec is a testing tool based on the concept of emergent design using specification. You may have heard of Behavior Driven Development (BDD), and PhpSpec is a tool to use at the spec level or SpecBDD. We also mentioned PhpSpec in our recent roundup of Laravel Testing Resources, which includes ways you can incorporate PhpSpec into your Laravel workflow if you are interested in trying out SpecBDD.

If you’ve never tried PhpSpec, one of the things that I love about PhpSpec is that the process generates code for you and guides you through the SpecBDD process through the command line. Let me show you what I mean with a quick tutorial.

Getting Started

If you want to follow along, make sure you have Composer installed and in your path. The first thing we’re going to do is create a new project. When I am tinkering with libraries, I like to create a project in a folder I call “Sandbox”:

$ mkdir -p ~/Code/sandbox/calculator-phpspec && cd $_
$ mkdir src/

$ composer init
$ composer require --dev phpspec/phpspec

Next, let’s add a PSR-4 namespace to our project’s composer.json file, in the end, your file will look similar to this:

{
    "name": "predmond/calculator-phpspec",
    "require": {},
    "require-dev": {
        "phpspec/phpspec": "^4.0"
    },
    "autoload": {
        "psr-4": {
            "Predmond\\Calculator\\": "src/"
        }
    }
}

Before we forget, let’s dump the autoloader so our namespace can be used later:

$ composer dump-autoload
Generating autoload files

You should be able to run the phpspec CLI now:

# I like to have /vendor/bin in my path
export PATH="./vendor/bin:$PATH"

# Or run it with the path
$ ./vendor/bin/phpspec
phpspec 4.0.3

Usage:
  command [options] [arguments]
...

Configuring Our Suite

PhpSpec will look for a configuration file in the root of the project for a phpspec.yml file. We can use this file to configure test suites, formatters, and other configuration rules.

The configuration documentation is pretty extensive, so you should check it out, but for our purposes, we just want to configure our PSR-4 namespace so I can walk you through the workflow of using PhpSpec to drive out a specification.

PhpSpec uses a top-level YAML key called “suites” so let’s define a default suite for our project’s namespace by creating a phpspec.yml file in the root of our project:

suites:
    default:
        namespace: Predmond\Calculator
        psr4_prefix: Predmond\Calculator
formatter.name: pretty

We can use this suite to create a calculator class and drive out a basic calculator spec. Let’s get to work!

The Workflow

Our workflow with PhpSpec will typically look like the following steps:

  1. Describe a Specification
  2. Run the Specification (Create the class if it doesn’t exist)
  3. Write an expected behavior
  4. Run the specification (create the method if it doesn’t exist)
  5. Write the implementation
  6. Verify the behavior
  7. Repeat

The way PhpSpec works does a good job of forcing you into this BDD workflow, which you might fight at first, but tends to be a reliable coding workflow once you get comfortable. Let’s go through a quick example of the workflow so you can give it a shot!

Describing a Spec

The first thing we need to do is define a specification. In our example, we are going to describe a calculator. You define a specification (think of it as creating a test class) by using the describe command. Run the following to create a Calculator spec:

$ ./vendor/bin/phpspec describe Predmond/Calculator/Calculator

# Or you can use backslashes with quotes
$ ./vendor/bin/phpspec describe "Predmond\Calculator\Calculator"

Running this command creates our specification file in spec/CalculatorSpec.php which looks like the following:

<?php

namespace spec\Predmond\Calculator;

use Predmond\Calculator\Calculator;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

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

Take note that we haven’t created the actual Calculator class yet. Our file includes one specification file (it_is_initializable)—PhpSpec uses it_ and its_ with public functions to find specifications.

Also, take note of the underscore method name convention, which is an opinionated style that tends to be more readable. One goal of BDD is using language friendly to humans to describe behavior.

Now that we have our specification, let’s use run to run our specs:

$ ./vendor/bin/phpspec run
      Predmond\Calculator\Calculator

  11  ! is initializable
        class Predmond\Calculator\Calculator does not exist.
...
1 specs
1 examples (1 broken)
18ms

Do you want me to create `Predmond\Calculator\Calculator` for you? [Y/n]

Enter “Y” and PhpSpec will create the Calculator class. Rerunning the specifications should make our it_is_initializable spec pass:

$ ./vendor/bin/phpspec run

      Predmond\Calculator\Calculator

  11  ✔ is initializable

1 specs
1 examples (1 passed)
9ms

You’ve just described your first specification, and PhpSpec took care of the details of creating methods and code for us! Let’s dig into creating more specifications.

Creating an Addition Specification

Let’s drive out a design for addition in our Calculator class. Our specifications are green, so we are ready to write a new specification before implementing it. Open up the spec/CalculatorSpec.php file and add the following:

function it_can_add_two_numbers()
{

}

I didn’t add any code because I want to show you how PhpSpec guides you on what to do next, which is pretty neat:

$ ./vendor/bin/phpspec run

      Predmond\Calculator\Calculator

  11  ✔ is initializable
  16  - can add two numbers
        todo: write pending example

1 specs
2 examples (1 passed, 1 pending)
11ms

PhpSpec is telling us that we have a pending specification. Let’s drive it out now:

function it_can_add_two_numbers()
{
    $this->add(2,3)->shouldReturn(5);
}

We are using the specification to say: “call add() with the parameters 2, and 3, and add() should return 5.

Here’s an important point to understand: $this is the object we are describing. You don’t need to construct the object as you would in other test suites:

# Other test suites
$calculator = new Calculator();

# PhpSpec $this is the Calculator
$this->add(); // And string some matching expectations...

If we try to run our specification now, PhpSpec will tell us the next step:

$ ./vendor/bin/phpspec run

      Predmond\Calculator\Calculator

  11  ✔ is initializable
  16  ! can add two numbers
        method Predmond\Calculator\Calculator::add not found.

----  broken examples

        Predmond/Calculator/Calculator
  16  ! can add two numbers
        method Predmond\Calculator\Calculator::add not found.

1 specs
2 examples (1 passed, 1 broken)
11ms

  Do you want me to create
  `Predmond\Calculator\Calculator::add()` for you? [Y/n]
$ ./vendor/bin/phpspec run

      Predmond\Calculator\Calculator

  11  ✔ is initializable
  16  ✘ can add two numbers
        expected [integer:5], but got null.

----  failed examples

        Predmond/Calculator/Calculator
  16  ✘ can add two numbers
        expected [integer:5], but got null.

1 specs
2 examples (1 passed, 1 failed)
11ms

PhpSpec will define the add() method on our implementation if we confirm “Y”. Kind of cool, eh?

Once you confirm, immediately our specification fails. That means it’s time to write the minimal amount of code to get the specification to pass.

Our Calculator class has been generated by PhpSpec at this point. Here’s what it should look like:

<?php

namespace Predmond\Calculator;

class Calculator
{
    public function add($argument1, $argument2)
    {
        // TODO: write logic here
    }
}

Based on our it_can_add_two_numbers() specification, PhpSpec has created a public add() method with two arguments defined. Let’s do the minimal amount to get this test passing:

public function add($argument1, $argument2)
{
    return 5;
}

Most of the time doing this minimal amount of code is ridiculous but stick with me because when you test more complex specification writing the minimum amount of code can help you build a robust specification.

Now that we’ve written the minimal amount of code, it’s time to rerun our specs:

$ ./vendor/bin/phpspec run

      Predmond\Calculator\Calculator

  11  ✔ is initializable
  16  ✔ can add two numbers

1 specs
2 examples (2 passed)
20ms

We have a passing spec! It’s not very useful—but it’s green.

Let’s add a few more examples to our specification to drive the implementation:

function it_can_add_two_numbers()
{
    $this->add(2, 3)->shouldReturn(5);
    $this->add(10, 0)->shouldReturn(10);
    $this->add(0, 0)->shouldReturn(0);
    $this->add(1, -2)->shouldReturn(-1);
}

If you rerun your specs, our addition spec will fail in a blaze of glory. We are ready to update our implementation to get things back to green now:

public function add($argument1, $argument2)
{
    return $argument1 + $argument2;
}

Running phpspec should result in both specifications passing.

Division

Let’s quickly write a more interesting specification: preventing a division by zero error. When this happens, we will throw an exception.

The specification might look like this:

function it_can_divide_two_numbers()
{
    $this->divide(10, 2)->shouldReturn(5);
}

function it_throws_a_division_by_zero_excption()
{
    $this->shouldThrow('\Predmond\Calculator\DivisionByZeroException')->duringDivide(10, 0);
}

And the implementation:

public function divide($argument1, $argument2)
{
    return $argument1 / $argument2;
}

If you run the specs you should get a division by zero warning:

$ ./vendor/bin/phpspec run

      Predmond\Calculator\Calculator

  11  ✔ is initializable
  16  ✔ can add two numbers
  24  ✔ can divide two numbers
  29  ✘ throws a division by zero exception
        expected exception of class "\Predmond\Calculator\Divi...", but got
        [exc:PhpSpec\Exception\Example\ErrorException("warning: Division by zero in
        /Users/predmond/Code/sandbox/calculator-phpspec/src/Calculator.php line 14")].

Let’s fix that by throwing the exception when the denominator is zero:

public function divide($numerator, $denominator)
{
    if ($denominator === 0) {
        throw new DivisionByZeroException();
    }

    return $numerator / $denominator;
}

And make sure to create the exception class in the src/ folder:

<?php

namespace Predmond\Calculator;

class DivisionByZeroException extends \Exception {}

And running our specs should pass now:

$ ./vendor/bin/phpspec run

      Predmond\Calculator\Calculator

  11  ✔ is initializable
  16  ✔ can add two numbers
  24  ✔ can divide two numbers
  29  ✔ throws a division by zero exception

1 specs
4 examples (4 passed)
13ms

Constructing Objects and Matchers

There are two important concepts I want to introduce before we wrap up: constructing objects and matchers.

Our Calculator class is pretty basic, but you will likely need to construct objects that have dependencies. You should read the object construction section to get an overview.

Matchers are used in PhpSpec to describe how an object should behave. You’ve already used the identity matcher when you called $this->add(2, 3)->shouldReturn(5);. The shouldReturn uses the identity operator (===) to make sure the spec matches expectations. The four types of identity matchers you can use are:

$this->calculatorMemory()->shouldBe(5);
$this->getTitle()->shouldBeEqualTo("Star Wars");
$this->getReleaseDate()->shouldReturn(233366400);
$this->getDescription()->shouldEqual("Inexplicably popular children's film");

You’ve also used the Throw Matcher when we expected our divide() method to throw a DivisionByZeroException exception.

You can check out the full matchers documentation and use it as a reference to work with PhpSpec.

Learn More

We’ve just scratched the surface of what PhpSpec can do. We didn’t demonstrate collaborators, but perhaps we can do a follow-up post with more examples. You should read through the whole PhpSpec manual, which isn’t very long. Once you get a handle on things, learn about Prophet Objects which include stubs, mocks, and spies.

I have a small HTML to AMP library that uses PhpSpec (and uses TravisCI). Mike Stowe has an excellent introductory article called What is Spec Driven Development that will give you an overview of SpecBDD.

Laravel News Partners

Newsletter

Join the weekly newsletter and never miss out on new tips, tutorials, and more.