Testing With PhpSpec
Published on by Paul Redmond
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-autoloadGenerating autoload files
You should be able to run the phpspec CLI now:
# I like to have /vendor/bin in my pathexport PATH="./vendor/bin:$PATH" # Or run it with the path$ ./vendor/bin/phpspecphpspec 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\Calculatorformatter.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:
- Describe a Specification
- Run the Specification (Create the class if it doesn’t exist)
- Write an expected behavior
- Run the specification (create the method if it doesn’t exist)
- Write the implementation
- Verify the behavior
- 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 specs1 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 specs1 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 specs2 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 specs2 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 specs2 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 specs2 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 specs4 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.