PHPSpec writes coded Examples of behaviour using a Domain Specific Language (DSL) for describing expectations. The DSL was designed to approximate plain grammatically accurate English so it is intuitive to use, read and comprehend.
The basic form of the DSL is to attach an Expectation (should or should not) and a Matcher (be, beAnInstanceOf, equal, etc.) to the value or object passed to a new Spec. This approach leads to a relatively easy to read sentance requiring minimal translation into the plain English (or other language!) we normally think in. Since the translation effort is minimised, and is closer to how we really think, it's invariably easy to review, critique and modify.
Example 5.5. Example Spec DSL: Bowling should not be an instance of Logger
$bowling = new Bowling;
$this->spec($bowling)->shouldNot->beAnInstanceOf('Logger');
In a PHPSpec Example method block, the DSL is instantiated using a
call to the PHPSpec_Context::spec(). This accepts
three possible parameter groupings.
A scalar value, i.e. string, integer, boolean, float, or array
An object
An object name, together with any constructor parameters
Example 5.6. Actual Term: Scalar Examples
$this->spec('i am a string')->should-beString();
$this->spec(567)->should->equal(567);
$this->spec(array(1, 2, 3))->shouldNot->beEmpty();
Example 5.7. Actual Term: Object Examples
$this->spec(new Bowling)->should->beAnInstanceOf('Bowling');
$bowling = new Bowling;
$this->spec($bowling)->shouldNot->havePlayers();
Example 5.8. Actual Term: Object Name With Constructor Params
$this->spec('Bowling', new Player('Joe'), new Player('Jim'))->should->havePlayers();
Just as with English, all expectations fall into one of two
possible classes. Those you expect to fail, and those you expect to
pass. Whether you wish a Matched Actual Value or an Unmatched Actual
Value to be interpreted as a pass depends on the use of the DSL
should or shouldNot
phrases.
All the examples below are expected to pass.
Example 5.9. Expectation Term: Various Passing Examples
$spec->( array() )->should->beEmpty();
$spec->('Bowling')->shouldNot->havePlayers();
$spec->('i am a string')->should->match("/^[a-z ]$/");
$spec->(is_int('string'))->shouldNot->beTrue();
Whereas Unit Testing frameworks rely on assertions, PHPSpec splits
the responsibility between an Expectation Term and a Matcher. A Matcher
is a simple object which compares an Actual Value Term with the expected
value passed to the Matcher method in the DSL for a positive or negative
match. The form of a Matcher is ruled by the
PHPSpec_Matcher_Interface interface so you can
write custom Matchers (pending feature).
An already expansive range of Matchers are provided by the PHPSpec framework. [Note: Some are still awaiting development.]
A Matcher is general appended as the last term to a Spec as demonstrated in earlier examples.
Note that all Matchers will return a boolean when called thus
ending the fluent interface of the Spec. Parameters marked
NULL generally mean a parameter is not required
(the expected value is implicit in the Matcher name).
Table 5.1. PHPSpec Matchers
| Matcher Method | Explanation |
|---|---|
|
|
Identical to using equal() and
reflects general English usage. |
bool beEqualTo (mixed
$expected)
|
Identical to using equal() and
reflects general English usage. |
bool equal (mixed
$expected)
|
Attempts to match the expected value on an equal basis intelligently comparing scalar values, object class, array content, or other metrics generally associated with two items being equivelant. |
bool beTrue (null
$expected)
|
Matches the actual value against
TRUE. |
bool beFalse (null
$expected)
|
Matches the actual value against
FALSE. |
bool beNull (null
$expected)
|
Checks if the actual value is
NULL. |
bool beEmpty (mixed
$expected)
|
Checks if the actual value is empty (using
empty()). |
|
|
Checks if the actual value is set (using
isset()). |
|
|
Determines if the actual value is both an object and an instance of the class type provided. |
|
|
Checks the value type accepting a string decription of the expected type (e.g. 'int', 'stdClass'). |
|
|
Checks if the actual value is an integer. This is a precise check - the string form of an integer will not match. |
|
|
Checks if the actual value is an array. |
|
|
Checks if the actual value is a string. |
|
|
Checks if the actual value is a float. |
|
|
Checks if the actual value is an object; does not perform type comparison on class type. |
|
|
Checks if the actual value is greater than
(>) the expected value
provided. |
|
|
Checks if the actual value is less than
(<) the expected value
provided |
|
|
Checks if the actual value is greater than or equal to
(>=) the expected value
provided |
|
|
Checks if the actual value is less than or equal to
(<=) the expected value
provided |
A Predicate Matcher is a Matcher which captures it's actual value from an
object being specified. It does so by seeking and then calling a
method of the form isSomething() or
hasSomething(). We saw this already in previous
DSL examples where the DSL method havePlayers()
is translated into a call to
Bowling::hasPlayers(). A boolean result from
the called method is then compared to a boolean
TRUE to check for a positive or negative
match.
Example 5.10. Example of Classes and Predicate Matcher Calls
class Insect {
public function isInsect() {
return true;
}
public function hasWings() {
return true;
}
}
class Flea extends Insect {
public function hasWings() {
return false; // Fleas are wingless blood sucking things
}
}
class DescribeFlea extends PHPSpec_Context {
public function itShouldBeAnInsect()
{
$flee = new Flea;
$this->spec($flea)->should->beAnInsect(); // Flea::isInsect() == TRUE
}
public function itShouldHaveNoWings()
{
$flee = new Flea;
$this->spec($flea)->shouldNot->haveWings(); // Flea::hasWings() == FALSE
}
}
Predicate Matcher methods in the DSL allow for the use of
be(), beA(),
beAn() variations which are primarily for
allowing grammatically correct structures and are otherwise identical.
Same applies to have(), haveA(), and haveAn().
The same variations are also searched for when matching to an object's
methods (even object methods can be grammatically correct!). This form
of matching will eventually be expanded to allow for other predicate
style calling methods. If you have any suggestions be sure to let us
know.