Skip to content

What it is

PHPUnit is the standard testing framework for PHP, with assertions, test doubles, data providers and code coverage reporting.

Test classes extend TestCase. Attributes replace the older annotations, and data providers drive parameterised tests.

Installation

composer require --dev phpunit/phpunit

Getting started

The smallest useful thing you can do with it, and what each part means.

Tests and data providers
<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\{Test, DataProvider};

final class SlugifyTest extends TestCase
{
    private Slugifier $sut;

    protected function setUp(): void
    {
        $this->sut = new Slugifier();   // fresh per test
    }

    #[Test]
    public function it_handles_empty_input(): void
    {
        self::assertSame('', $this->sut->slugify(''));
    }

    #[Test]
    #[DataProvider('cases')]
    public function it_slugifies(string $input, string $expected): void
    {
        self::assertSame($expected, $this->sut->slugify($input));
    }

    public static function cases(): iterable
    {
        yield 'spaces'      => ['Hello World', 'hello-world'];
        yield 'punctuation' => ['C++ & Go!', 'c-go'];
    }
}
assertSame checks type as well as value, unlike assertEquals which uses loose comparison — in PHP that distinction matters and assertSame should be the default.

Advanced usage

Where the library earns its place over a simpler alternative.

Test doubles and exceptions
<?php
#[Test]
public function it_returns_the_title(): void
{
    $repo = $this->createMock(BookRepository::class);
    $repo->expects(self::once())
         ->method('find')
         ->with(42)
         ->willReturn(new Book('Dune'));

    $service = new BookService($repo);
    self::assertSame('Dune', $service->title(42));
}

#[Test]
public function it_rejects_a_missing_book(): void
{
    $this->expectException(NotFoundException::class);
    $this->expectExceptionMessage('Book 99 not found');

    (new BookService($this->createStub(BookRepository::class)))->title(99);
}
createMock verifies expectations; createStub only returns values. Using a stub where you meant a mock produces a test that asserts nothing about the interaction.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Test passes but asserts nothing
A stub was used where a mock with expects() was needed, or the assertion is unreachable. Enable failOnRisky in the configuration.
Annotations stopped working after upgrading
PHPUnit 11 removed docblock annotations. Migrate to attributes such as #[Test] and #[DataProvider].

Best practices

  • Prefer assertSame over assertEquals — loose comparison hides type bugs.
  • Use attributes rather than docblock annotations; annotations are removed in PHPUnit 11+.
  • Use data providers instead of near-duplicate test methods.
  • Consider Pest if the team prefers a lighter syntax; it runs on PHPUnit underneath.

Background

Why it exists, and what it was reacting to.

Written by Sebastian Bergmann, PHPUnit is the foundation of PHP testing — Laravel's and Symfony's own test tooling both build on it.