Testing With PhpSpec

Tutorials

September 15th, 2017

testing-with-phpspec.jpg

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”:

1$ mkdir -p ~/Code/sandbox/calculator-phpspec && cd $_
2$ mkdir src/
3
4$ composer init
5$ 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:

1{
2 "name": "predmond/calculator-phpspec",
3 "require": {},
4 "require-dev": {
5 "phpspec/phpspec": "^4.0"
6 },
7 "autoload": {
8 "psr-4": {
9 "Predmond\\Calculator\\": "src/"
10 }
11 }
12}

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

1$ composer dump-autoload
2Generating autoload files

You should be able to run the phpspec CLI now:

1# I like to have /vendor/bin in my path
2export PATH="./vendor/bin:$PATH"
3
4# Or run it with the path
5$ ./vendor/bin/phpspec
6phpspec 4.0.3
7
8Usage:
9 command [options] [arguments]
10...

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:

1suites:
2 default:
3 namespace: Predmond\Calculator
4 psr4_prefix: Predmond\Calculator
5formatter.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:

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

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

1<?php
2
3namespace spec\Predmond\Calculator;
4
5use Predmond\Calculator\Calculator;
6use PhpSpec\ObjectBehavior;
7use Prophecy\Argument;
8
9class CalculatorSpec extends ObjectBehavior
10{
11 function it_is_initializable()
12 {
13 $this->shouldHaveType(Calculator::class);
14 }
15}

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:

1$ ./vendor/bin/phpspec run
2 Predmond\Calculator\Calculator
3
4 11 ! is initializable
5 class Predmond\Calculator\Calculator does not exist.
6...
71 specs
81 examples (1 broken)
918ms
10
11Do 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:

1$ ./vendor/bin/phpspec run
2
3 Predmond\Calculator\Calculator
4
5 11 ✔ is initializable
6
71 specs
81 examples (1 passed)
99ms

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:

1function it_can_add_two_numbers()
2{
3
4}

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:

1$ ./vendor/bin/phpspec run
2
3 Predmond\Calculator\Calculator
4
5 11 ✔ is initializable
6 16 - can add two numbers
7 todo: write pending example
8
9
101 specs
112 examples (1 passed, 1 pending)
1211ms

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

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

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:

1# Other test suites
2$calculator = new Calculator();
3
4# PhpSpec $this is the Calculator
5$this->add(); // And string some matching expectations...

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

1$ ./vendor/bin/phpspec run
2
3 Predmond\Calculator\Calculator
4
5 11 ✔ is initializable
6 16 ! can add two numbers
7 method Predmond\Calculator\Calculator::add not found.
8
9---- broken examples
10
11 Predmond/Calculator/Calculator
12 16 ! can add two numbers
13 method Predmond\Calculator\Calculator::add not found.
14
15
161 specs
172 examples (1 passed, 1 broken)
1811ms
19
20 Do you want me to create
21 `Predmond\Calculator\Calculator::add()` for you? [Y/n]
1$ ./vendor/bin/phpspec run
2
3 Predmond\Calculator\Calculator
4
5 11 ✔ is initializable
6 16 ✘ can add two numbers
7 expected [integer:5], but got null.
8
9---- failed examples
10
11 Predmond/Calculator/Calculator
12 16 ✘ can add two numbers
13 expected [integer:5], but got null.
14
15
161 specs
172 examples (1 passed, 1 failed)
1811ms

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:

1<?php
2
3namespace Predmond\Calculator;
4
5class Calculator
6{
7 public function add($argument1, $argument2)
8 {
9 // TODO: write logic here
10 }
11}

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:

1public function add($argument1, $argument2)
2{
3 return 5;
4}

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:

1$ ./vendor/bin/phpspec run
2
3 Predmond\Calculator\Calculator
4
5 11 ✔ is initializable
6 16 ✔ can add two numbers
7
8
91 specs
102 examples (2 passed)
1120ms

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:

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

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:

1public function add($argument1, $argument2)
2{
3 return $argument1 + $argument2;
4}

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:

1function it_can_divide_two_numbers()
2{
3 $this->divide(10, 2)->shouldReturn(5);
4}
5
6function it_throws_a_division_by_zero_excption()
7{
8 $this->shouldThrow('\Predmond\Calculator\DivisionByZeroException')->duringDivide(10, 0);
9}

And the implementation:

1public function divide($argument1, $argument2)
2{
3 return $argument1 / $argument2;
4}

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

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

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

1public function divide($numerator, $denominator)
2{
3 if ($denominator === 0) {
4 throw new DivisionByZeroException();
5 }
6
7 return $numerator / $denominator;
8}

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

1<?php
2
3namespace Predmond\Calculator;
4
5class DivisionByZeroException extends \Exception {}

And running our specs should pass now:

1$ ./vendor/bin/phpspec run
2
3 Predmond\Calculator\Calculator
4
5 11 ✔ is initializable
6 16 ✔ can add two numbers
7 24 ✔ can divide two numbers
8 29 ✔ throws a division by zero exception
9
10
111 specs
124 examples (4 passed)
1313ms

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:

1$this->calculatorMemory()->shouldBe(5);
2$this->getTitle()->shouldBeEqualTo("Star Wars");
3$this->getReleaseDate()->shouldReturn(233366400);
4$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.

Filed in:

Paul Redmond

Full stack web developer. Author of Lumen Programming Guide and Docker for PHP Developers.