100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
PHP

Unit Testing with PHPUnit

Get started with PHPUnit, PHP's most widely used testing framework, covering test case structure, assertions, data providers, and mocking dependencies.

Testing & EcosystemIntermediate10 min readJul 9, 2026
Analogies

Unit Testing with PHPUnit

PHPUnit is the standard unit testing framework for PHP, used to verify that individual units of code — typically a single method or class — behave correctly in isolation. A PHPUnit test is a method inside a class extending PHPUnit\Framework\TestCase, whose name conventionally begins with test (or is annotated with the #[Test] attribute), and which uses assertion methods like assertSame() or assertTrue() to check expected outcomes against actual results.

🏏

Cricket analogy: A net-practice session tests one specific skill in isolation — just facing yorkers from a bowling machine — rather than a full match, and the coach's checklist marks each ball as 'cleared' or 'missed,' much like a PHPUnit test method asserting one expected outcome.

Writing your first test case

Each test method should focus on one behavior and follow the Arrange-Act-Assert pattern: set up the inputs and any dependencies, invoke the code under test, then assert the outcome. PHPUnit provides dozens of assertion methods beyond the basics — assertCount(), assertInstanceOf(), assertJson(), assertStringContainsString() — chosen for how precisely and readably they express the expectation, and each failure message clearly reports the expected versus actual value.

🏏

Cricket analogy: A fielding drill follows set-up, execute, check: place the cones (arrange), have the fielder throw at the stumps (act), then verify hit or miss with a precise ruling like 'direct hit' rather than a vague 'good throw' (assert) — like PHPUnit's Arrange-Act-Assert with precise assertions.

php
<?php
declare(strict_types=1);

final class Money
{
    public function __construct(private readonly int $cents) {}

    public function add(Money $other): self
    {
        return new self($this->cents + $other->cents);
    }

    public function cents(): int
    {
        return $this->cents;
    }
}

final class MoneyTest extends \PHPUnit\Framework\TestCase
{
    public function testAddingTwoAmountsSumsTheirCents(): void
    {
        $sum = (new Money(150))->add(new Money(250));

        $this->assertSame(400, $sum->cents());
    }

    #[\PHPUnit\Framework\Attributes\DataProvider('invalidAmounts')]
    public function testConstructorRejectsNegativeCents(int $cents): void
    {
        $this->expectException(\InvalidArgumentException::class);

        new Money($cents);
    }

    public static function invalidAmounts(): array
    {
        return [[-1], [-100], [PHP_INT_MIN]];
    }
}

Data providers for parameterized tests

Rather than duplicating near-identical test methods for different inputs, a data provider — a static method returning an array of argument sets, referenced via the #[DataProvider] attribute — lets PHPUnit run the same test logic once per data set, reporting each as a separate result. This keeps tests DRY while still surfacing exactly which input case failed.

🏏

Cricket analogy: Rather than running a separate net session for every possible pitch condition, a coach feeds a bowling machine a list of scenarios — damp pitch, dry pitch, footmarks — and reuses the same batting drill for each, reporting each scenario's result separately, like PHPUnit's #[DataProvider].

Mocking dependencies

A unit test should isolate the class under test from its collaborators. PHPUnit's built-in mocking API, via createMock() or getMockBuilder(), generates a test double for an interface or class that lets you stub return values and assert that specific methods were called with expected arguments, without exercising the real dependency's logic — essential when the real dependency talks to a database, an external API, or the filesystem.

🏏

Cricket analogy: A batting simulator lets a player face a virtual bowler that mimics a specific bowling style without needing the real touring pace attack to fly in for practice, letting the coach verify technique in isolation, much like PHPUnit's mocking API standing in for a real dependency.

PHPUnit test method names are conventionally verbose and descriptive, like testAddingTwoAmountsSumsTheirCents — since a failing test's name is often the first (and sometimes only) thing a developer reads in CI output, a clear name communicates intent faster than a comment would.

Overusing mocks can produce tests that pass even when the real integration is broken, because every collaborator has been replaced with a stand-in that always behaves as expected. Reserve mocking for genuine external boundaries (network, filesystem, time) and complement unit tests with integration tests that exercise real collaborators.

Running tests and configuration

Tests are executed with vendor/bin/phpunit, typically configured via a phpunit.xml file that specifies test suite locations, bootstrap files, and code coverage settings. Composer conventionally installs PHPUnit as a dev dependency so it never ships to production, and CI pipelines run the same command to gate merges on a passing test suite.

🏏

Cricket analogy: A national team's fitness-clearance protocol runs the same standardized battery of tests before every single tour, configured once in a master checklist, and selectors won't approve a squad until every player clears it, much like CI gating merges on vendor/bin/phpunit passing.

  • PHPUnit test classes extend TestCase; test methods are named test* or use the #[Test] attribute.
  • Follow Arrange-Act-Assert and choose the most specific assertion method for clear failure messages.
  • Data providers (#[DataProvider]) run one test method across many input sets without duplicating code.
  • createMock()/getMockBuilder() create test doubles that isolate the unit under test from its collaborators.
  • Excessive mocking can hide real integration bugs — pair unit tests with integration tests at genuine boundaries.
  • Tests run via vendor/bin/phpunit, configured through phpunit.xml, and are installed as a Composer dev dependency.

Practice what you learned

Was this page helpful?

Topics covered

#PHP#PHPProgrammingStudyNotes#Programming#UnitTestingWithPHPUnit#Unit#Testing#PHPUnit#Writing#StudyNotes#SkillVeris