Unit Testing in PHP with PHPSpec: Principles, Setup, and Practical Examples

This article explains why unit testing is essential for PHP projects, introduces SOLID design principles, shows how to choose and install PHPSpec, demonstrates writing specifications, mocking, argument matching, exception testing, and configuring code‑coverage reports, all with concrete code examples.

360 Tech Engineering
360 Tech Engineering
360 Tech Engineering
Unit Testing in PHP with PHPSpec: Principles, Setup, and Practical Examples

Improving PHP code quality requires automated checks and thorough unit tests; this guide continues the series on PHP quality by focusing on unit testing with PHPSpec.

Unit tests verify individual pieces of code under the assumption that everything works as expected, and they rely on SOLID principles—especially the Single Responsibility, Liskov Substitution, and Dependency Inversion principles—to keep code testable.

While PHPUnit is a common choice, the author prefers PHPSpec for its behavior‑driven style. Installation is straightforward: $ php composer.phar require --dev phpspec/phpspec After installing, a PHPSpec target can be added to build.xml:

<target name="phpspec">
    <exec executable="bin/phpspec" passthru="true" checkreturn="true">
        <arg line="run --format=pretty"/>
    </exec>
</target>
... 
<target name="run" depends="phpcs,phpcpd,phan,phpspec"/>

A typical PHPSpec spec file (e.g., spec/Domain/PriceComparatorSpec.php) demonstrates dependency injection, mock objects, and expectations:

// spec/Domain/PriceComparatorSpec.php
<?php
namespace spec\Domain;
use Domain\Price;
use Domain\PriceConverter;
use PhpSpec\ObjectBehavior;

class PriceComparatorSpec extends ObjectBehavior {
    public function let(PriceConverter $converter) {
        $this->beConstructedWith($converter);
    }
    public function it_should_return_equal() {
        $price1 = new Price(100, 'EUR');
        $price2 = new Price(100, 'EUR');
        $this->compare($price1, $price2)->shouldReturn(0);
    }
    public function it_should_convert_first(PriceConverter $converter) {
        $price1 = new Price(100, 'EUR');
        $price2 = new Price(100, 'PLN');
        $priceConverted = new Price(25, 'EUR');
        $converter->convert($price2, 'EUR')->willReturn($priceConverted);
        $this->compare($price1, $price2)->shouldReturn(1);
    }
}

PHPSpec provides a human‑readable syntax for mocking. For example, to assert a method call: $mockObject->someMethod("desired value")->shouldBeCalled(); To stub a return value:

$mockObject->someFunction("some input")->willReturn("some value");

If the exact argument is irrelevant, use AnyValueToken:

use Prophecy\Argument\Token\AnyValueToken;
$mockObject->someFunction(new AnyValueToken())->willReturn(true);

For more complex checks, a callback token can evaluate arguments:

use Prophecy\Argument\Token\CallbackToken;
$checker = function (Message $message) use ($to, $text) {
    return $message->to === $to && $message->text === $text;
};
$msgSender->send(new CallbackToken($checker))->shouldBeCalled();

Exceptions can be asserted with:

$this->shouldThrow(\DomainException::class)->during('execute', [$command, $responder]);

Code‑coverage reporting is available via the leanphp/phpspec-code-coverage extension. Install it:

$ php composer.phar require --dev leanphp/phpspec-code-coverage

Enable the extension in phpspec.yml:

extensions:
  LeanPHP\PhpSpec\CodeCoverage\CodeCoverageExtension: ~

Running tests with phpdbg speeds up coverage generation: $ phpdbg -qrr phpspec run A dedicated phpspec target can be added to build.xml to use phpdbg:

<target name="phpspec">
    <exec executable="phpdbg" passthru="true" checkreturn="true">
        <arg line="-qrr bin/phpspec run --format=pretty"/>
    </exec>
</target>

The generated coverage report appears in the coverage/ directory as a browsable HTML page.

In conclusion, write unit tests as early and as often as possible; they run quickly, catch runtime‑only bugs, and encourage a more modular, testable codebase.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

code coverageunit testingMockingSOLIDPHPSpec
360 Tech Engineering
Written by

360 Tech Engineering

Official tech channel of 360, building the most professional technology aggregation platform for the brand.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.