1. Code
  2. BDD

Understanding PhpSpec


If you compare PhpSpec to other testing frameworks, you will find that it is a very sophisticated and opinionated tool. One of the reasons for this, is that PhpSpec is not a testing framework like the ones you already know. 

Instead, it is a design tool that helps describing behavior of software. A side effect of describing the behavior of software with PhpSpec, is that you will end up with specs that will also serve as tests afterwards.

In this article, we will take a look under the hood of PhpSpec and try to gain a deeper understanding of how it works and how to use it.

If you want to brush up on phpspec, take a look at my getting started tutorial.

In This Article...

  • A Quick Tour Of PhpSpec Internals
  • The Difference Between TDD and BDD
  • How is PhpSpec different (from PHPUnit)
  • PhpSpec: A Design Tool

A Quick Tour Of PhpSpec Internals

Let's start out by looking at some of the key concepts and classes that forms PhpSpec.

Understanding $this

Understanding what $this refers to is key to understand how PhpSpec differs from other tools. Basically, $this refer to an instance of the actual class under test. Let's try to investigate this a little more in order to better understand what we mean.

First of all, we need a spec and a class to play around with. As you know, PhpSpec's generators makes this super easy for us:

Next up, open the generated spec file and let's try to get a little more information about $this:

get_class() returns the class name of a given object. In this case, we just throw $this in there to see what it returns:

Okay, so not too surprisingly, get_class() tells us that $this is an instance of spec\Suhm\HelloWorldSpec. This makes sense since, after all, this is just plain old PHP code. If instead we used get_parent_class(), we would getPhpSpec\ObjectBehavior, since our spec extends this class.

Remember, I just told you that $this actually referred to the class under test, which would beSuhm\HelloWorld in our case? As you can see, the return value of get_class($this) is contradicting with $this->shouldHaveType('Suhm\HelloWorld');.

Let's try something else out:

With the above code, we try to call a method named dumpThis() on the HelloWorld instance. We chain an expectation to the method call, expecting the return value of the function to be a string containing"spec\Suhm\HelloWorldSpec". This is the return value from get_class() on the line above.

Again, the PhpSpec generators can help us with some scaffolding:

Let's try to call get_class() from within dumpThis() too:

Again, not surprisingly, we get:

It looks like we are missing something here. I started out by telling you that $this is not referring to what you think it does, but so far our experiments have shown nothing unexpected. Except one thing: How could we call $this->dumpThis() before it was existing without PHP squeaking at us?

In order to understand this, we need to dive into the PhpSpec source code. If you want to take a look yourself, you can read the code on GitHub.

Take a look a the following code from src/PhpSpec/ObjectBehavior.php (the class that our spec extends):

The comments give most of it away: "Proxies all call to the PhpSpec subject". The PHP __call method is a magic method called automatically whenever a method is not accessible (or non-existing). 

This means that when we tried to call $this->dumpThis(), the call was apparently proxied to the PhpSpec subject. If you look at the code, you can see that the method call is proxied to $this->object. (The same goes for properties on our instance. They are all proxied to the subject as well, using other magic methods. Take a look in the source to see for yourself.)

Let's consult get_class() one more time and see what it has to say about $this->object:

And look what we get:

More on Subject

Subject is a wrapper and implements the PhpSpec\Wrapper\WrapperInterface. It is a core part of PhpSpec and allows for all the [seemingly] magic that the framework can do. It wraps an instance of the class we are testing, so that we can do all kinds of things such as calling methods and properties that does not exists and set expectations. 

As mentioned, PhpSpec is very opinionated towards how you should write and spec your code. One spec maps to one class. You have only one subject per spec, which PhpSpec will carefully wrap for you. The important thing to note about this is that this allows you to use $this as if it was the actual instance and makes for really readable and meaningful specs.

PhpSpec contains a Wrapper which takes care of instantiating the Subject. It packs the Subject with the actual object we are spec'ing. Since Subject implements the WrapperInterface it must have a getWrappedObject()method that gives us access to the object. This is the object instance we were searching for earlier with get_class()

Let's try it out again:

And there you go:

Even though a lot of things are going on behind the scene, in the end we are still working with the actual object instance of Suhm\HelloWorld. All is well.

Earlier, when we called $this->dumpThis(), we learned how the call was actually proxied to the Subject. We also learned that Subject is only a wrapper and not the actual object. 

With this knowledge, it is clear that we are not able to call dumpThis() on Subject without another magic method. Subject has a __call() method as well:

This method does one of two things. First, it checks if the method name begins with 'should'. If it does, it is an expectation, and the call is delegated to a method called callExpectation(). If not, the call is instead delegated to an instance of PhpSpec\Wrapper\Subject\Caller

We will ignore the Caller for now. It, too, contains the wrapped object and knows how to call methods on it. The Caller returns a wrapped instance when it calls methods on the subject, allowing us to chain expectations to methods, like we did with dumpThis().

Instead, let's take a look at the callExpectation() method:

This method is responsible for building an instance of PhpSpec\Wrapper\Subject\Expectation\ExpectationInterface. This interface dictates a match() method, which the callExpectation() calls to check the expectation. There are four different kinds of expectations: PositiveNegativePositiveThrow and NegativeThrow. Each of these expectations contains an instance of PhpSpec\Matcher\MatcherInterface that the match() method uses. Let's look at matchers next.


Matchers are what we use to determine the behavior of our objects. Whenever we write should... or shouldNot..., we are using a matcher. You can find a comprehensive list of PhpSpec matchers on my personal blog.

There are many matchers included with PhpSpec, all of which extends the PhpSpec\Matcher\BasicMatcher class, which implements the MatcherInterface. The way matchers work is pretty straight forward. Let's take a look at it together and I encourage you to take a look at the source code as well.

As an example, let's look at this code from the IdentityMatcher:

The supports() method is dictated by the MatcherInterface. In this case, four aliases are defined for the matcher in the $keywords array. This will allow the matcher to support either: shouldReturn()shouldBe()shouldEqual() orshouldBeEqualTo(), or shouldNotReturn()shouldNotBe()shouldNotEqual() or shouldNotBeEqualTo().

From the BasicMatcher, two methods are inherited: positiveMatch() and negativeMatch(). They look like this:

The positiveMatch() method throws an exception if the matches() method (abstract method that matchers must implement) returns false. The negativeMatch() method works the opposite way. The matches() method for theIdentityMatcher uses the === operator to compare the $subject with the argument supplied to the matcher method:

We could use the matcher like this:

Which would eventually call negativeMatch() and make sure that matches() returns false.

Take a look at some of the other matchers and see what they do!

Promises of More Magic

Before we end this short tour of PhpSpec's internals, let's have a look at one more piece of magic:

By adding the type hinted $object parameter to our example, PhpSpec will automagically use reflection to inject an instance of the class for us to use. But with the things we saw already, do we really trust that we really get an instance of StdClass? Let's consult get_class() one more time:

Nope. Instead of StdClass we get an instance of PhpSpec\Wrapper\Collaborator. What is this about?

Like SubjectCollaborator is a wrapper and implements the WrapperInterface. It wraps an instance of\Prophecy\Prophecy\ObjectProphecy, which stems from Prophecy, the mocking framework that comes together with PhpSpec. Instead of an StdClass instance, PhpSpec gives us a mock. This makes mocking laughably easy with PhpSpec and allows us to add promises to our objects like this:

With this short tour of parts of PhpSpec's internals, I hope you see that it is more than a simple testing framework.

The Difference Between TDD And BDD

PhpSpec is a tool for doing SpecBDD, so in order to get a better understanding, let's take a look at the differences between test driven development (TDD) and behavior driven development (BDD). Afterward, we will take a quick look at how PhpSpec differs from other tools such as PHPUnit.

TDD is the concept of letting automated tests drive the design and implementation of code. By writing small tests for each feature, before actually implementing them, when we get a passing test, we know that our code satisfy that specific feature. With a passing test, after refactoring, we stop coding and write the next test instead. The mantra is "red", "green", "refactor"!

BDD has its origin from - and is very similar to - TDD. Honestly, it is mainly a question of wording, which is indeed important since it can change the way we think as developers. Where TDD talks about testing, BDD talks about describing behavior. 

With TDD we focus on verifying that our code works the way we expect it to work, whereas with BDD, we focus on verifying that our code actually behave the way that we want it to. A main reason for the emergence of BDD, as an alternative to TDD, is to avoid using the word "test". With BDD we are not really interested in testing the implementation of our code, we are more interested in testing what it does (its behavior). When we do BDD, instead of TDD, we have stories and specs. These makes writing traditional tests redundant.

Stories and specs are closely tied to the expectations of the project stakeholders. Writing stories (with a tool such as Behat), would preferably happen together with the stakeholders or domain experts. The stories cover the external behavior. We use specs to design the internal behavior needed to fullfill the steps of the stories. Each step in a story might require multiple iterations with writing specs and implementing code, before it is satisfied. Our stories, together with our specs, helps us to make sure that not only are we building a working thing, but that it is also the right thing. As so, BDD has a lot to do with communication.

How is PhpSpec Different From PHPUnit?

A few months ago, a notable member of the PHP community, Mathias Verraes, posted "A unit testing framework in a tweet" on Twitter. The point was to fit the source code of a functional unit testing framework into one single tweet. As you can see from the gist, the code is truly functional, and allows you to write basic unit tests. The concept of unit testing is actually pretty simple: Check some sort of assertion and notify the user of the result.

Of course, most testing frameworks, such as PHPUnit, are indeed much more advanced, and can do much more than Mathias' framework, but it still shows an important point: You assert something and then you framework runs that assertion for you.

Let's take a look at a very basic PHPUnit test:

Would you be able to write a super simple implementation of a testing framework that could run this test? I am pretty sure that the answer is "yes" you could do that. After all, the only thing the assertTrue() method has to do is to compare a value against true and throw an exception if it fails. At its core, what is going on is actually pretty straight forward.

So how is PhpSpec different? First of all, PhpSpec is not a testing tool. Testing your code is not the main goal of PhpSpec, but it becomes a side effect if you use it to design your software by incrementally adding specs for the behavior (BDD). 

Second of all, I think the above sections should have already made it clear how PhpSpec is different. Still, let's compare some code:

Because PhpSpec is highly opinionated and make some assertions as to how our code is designed, it gives us a very easy way to describe our code. On the other hand, PHPUnit does not make any assertions towards our code and lets us do pretty much what we want. Basically all PHPUnit does for us in this example, is to run $object against theinstanceof operator. 

Even though PHPUnit might seem easier to get started with (i don't think it is), if you are not careful, you can easily fall into traps of bad design and architecture because it lets you do almost anything. That being said, PHPUnit can still be great for many use cases, but it is not a design tool like PhpSpec. There is no guidance - you have to know what you are doing.

PhpSpec: A Design Tool

From the PhpSpec website, we can learn that PhpSpec is:

A php toolset to drive emergent design by specification.

Let me say it one more time: PhpSpec is not a testing framework. It is a development tool. A software design tool. It is not a simple assertion framework that compares values and throws exceptions. It is a tool that assists us in designing and building well-crafted code. It requires us to think about the structure of our code and enforces certain architectural patterns, where one class maps to one spec. If you break the single responsibility principle and need to partially mock something, you will not be allowed to do it.

Happy spec'ing!

Oh! And finally, =since PhpSpec itself is spec'ed, I suggest that you go to GitHub and explore the source to learn more.

Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.