Welcome to another episode of Magecasts.io, On our previous PHPSpec episode we covered how to setup and configured PHPSpec as well we went through a basic example by generating a new Product class.
In this lesson, we will go over one key concept that we need to master in order use PHPSpec and SpecBDD effectively.
If you are already familiar with PHPSpec matchers, or just like to skip directly to the demonstration with Magento click the link below.
Anyone coming from a PHPUnit background should be familiar with the concept of Assertions, which are used in a unit test to check for an expected outcome.
However, in PHPspec instead of having assertions we make use of matchers.
Matchers are special language constructs that we use to specify the behavior of the object we are trying to test.
But you might ask, why is the syntactic difference so important? Match, Assert they kind of sounds the same; what's the difference?
Well to understand that we need to understand how TDD and BDD differ from each other and to put in a sentence:
Behavior Driven development is often referred as "test-driven development done right"
BDD changes the vocabulary we use to create our specifications and by doing so forces developers not only to write tests but to design their code as they go along.
We are no longer mindlessly writing tests, just to validate a preconceived and half-baked idea of how the code should look like.
No, we are thinking about how the code should behave and describing that without directly thinking about the implementation.
This process is referred as emergent design and is outside of the scope of this particular lesson; but for now understanding the general concept is enough.
To do a quick recap, matchers are:
- Used to describe how and object should behave.
- Equivalent of assertions on PHPUnit.
- A way to verify behavior instead of output.
Back to matchers, PHPspec provides 13 different types of matchers designed to test different sets of behavior.
Let's quick go over each of them and some examples:
This is the equivalent of the === (identical) operator in PHP, with this type of operator both type and value need to identical for the matcher to return true.
Similar to identity matcher, but the comparison uses the == (equal) operator in PHP, which only compares against the value, so in this case 1 and "1" would return true.
Now, let's move to a more interesting matcher. The throw matcher is used to verify that a specific exception was thrown.
In our example we are saying when the function is called with this set of parameters, it should throw an exception.
Personally, I have found this matcher to be highly useful when creating payment gateways or custom shopping cart rules implementations since, in both cases, there are multiple things that need to be validated and tested.
This type of matcher is used to verify that the object that we are describing implements an interface or extends a specific class. This could be useful if you want to verify that custom model extends the right Magento class.
ObjectState is useful if we want to check the state of our object, now the way these particular matcher works is by expecting our code to follow specific conventions.
By looking at our examples:
// calls isEnabled() on our subject class $this->shouldBeEnabled(); // calls hasPayment() on our subject class $this->shouldHavePayment();
ObjectState will always assume that:
- The functions will always start with either is or has
- The functions will always return a boolean value
The count matchers is a handy way of verifying collections, to implement this matcher the returned value most be an array or an object that implements the Countable interface.
The scalar matcher is used to specify that the value returned by a method should a specific primitive type, like string, array, boolean.
Before we continue, let me do on a quick recap of what we have seen so far.
- Matchers follow a naming convention, they all start with the keyword should
- Most matchers have syntactic variations that behave the same but are designed to make the resulting specification more readable.
- Specifications are not only tests but also tell a story.
In my opinion, the last point is what makes PHPspec and BDD incredibly appealing and to fun to use. Is not just about the testing or the behavior; is also about telling a story about our code.
So far, we have seen the most common types of matchers; those are the ones that likelihood you would be using 90% of the time and for most tests, the 6 remaining matchers are used to assert arrays and strings and we will look at them quickly.
Out of the box PHPspec provides three matchers to assert the contents of an array:
Which is used to validate that a method should return an array that contains a specific value, for example, it could be used to test a Magento model toOptionArray() function and make sure it returns at least one of the expected values.
Similar to the way ArrayContain works, ArrayKeyWithValue will assert that a specific value exists on an array with that addition it will also check that value is assigned to a specific array key.
With ArrayKey, we can assert only the existence of a specific key inside of our returning array without taking into account the value of said key.
The following three matchers are used to handle string assertions, and they are:
Which will check that a method returns a string starting with a specific substring.
Which will check that a method returns a string ending with a specific substring.
And StringRegex, which allow us to assert that a string match a particular regular expression, of the three this is potentially the most useful for Magento developers.
As we have seen so far, PHPSpec provides a comprehensive set of matchers that should cover most assertion types for most projects.
However, and this is especially true in the case of us Magento developers, there will be cases where you need to do assertions that are not covered by the standard matchers
Does this mean we should drop PHPSpec and go back to use PHPUnit? Well, ... of course not.
PHPspec has our back and provides a way to create custom matchers for our project. These matchers are referred as Inline Matchers and let's see how we can use inline matchers to assert Magento specific behavior.
For this example, we are going to work on a payment method specification, keep in mind we won't go over the specifics of how to setup Magento and PHPspec just yet.
For this lesson, we would just see how we can apply matchers into a real world context.
In this example, we will be working on setting up a payment method class, as well a few custom matchers to test some of the specific methods like canInvoice() and canAuthorize()
Currently, I'm using the Magespec extension, a PHPSpec plugin that add Magento specific commands to PHPSpec, in the next lesson we will cover how to install and set Magespec.
The first thing we will want to do is describe our payment model, in this case, we will create a new custom Magento module call Magecasts Expayment
As we learned before the command will generate our spec class and if we run PHPSpec again, it will also generate the corresponding Magento class in the right folder structure.
As well the extension app/et/modules XML file. Let's go ahead and open our favorite editor and view the generated spec class.
As we can see is empty and extending the Magento Abstract Model.
Let's go back and open our spec file, since we are building a payment method, it makes sense to extend the MagePaymentModel_Cc class, for which we can create our first specification making use of the shouldHaveType matcher.
Once our specification has been created, we can save our spec file and try to run our test.For which we should see and a failing result, since our method class still extends the base abstract model.
In true TDD fashion, now that we are red we can go ahead and change some code to make our test pass.So let's go back to the method class and change it to extend the MagePaymentModel_Cc class.
After that, we can save our file and run PHPSpec one more time, for which we should see all test passing.
Let's go back one more time to our spec file and continue describing our payment model.As any standard payment model, our example should be able to authorize a payment method.
In Magento, payment methods use class properties to define what the are capable of, and the abstract payment class defines gets for each of this class properties like canAuthorize().
Let's go ahead and run PHPSpec one more time with our new specification, in this case, we are expecting to see a failing test since our Expayment extension hasn't declared any of these properties yet.
And now that we are we a failing test we can go back into the payment class and make the necessary code changes in order to make the test pass.
We can easily make our test pass by simply declaring a new class property called _canAuthorize.
Finally, we can confirm our code changes are correct by running our test one more time.
So far we have been able to work only using the predefined matchers, but what if we want to assert things differently or in the case of the payment model we want a matcher that is more readable and easier to understand.
We are going to defined a new matcher called shouldBeAbleTo which will take a single parameter; internally this matcher will take the parameter and call the specific payment method getter.
In PHPSpec, we can define matchers inside a spec file by create a new public function called getMatchers.
Inside that function, we define an array with the alias of our matcher as the value an inline function what will implement the assertions.
Some examples of how we might use this matcher are:
Now that we have declared our customer matcher, let's go ahead and rewrite our specifications to make use of the new matcher. For this example, we are implementing
We have only started to scratch the surface of all the functionality PHPspec has to offer, in our next lesson we are going to continue working on an example payment extension for Magento as well we will look in more detail what kind of setup is needed.