SuperAPI AI Directory

phpunit

Testing & quality

View repository

Updated 10-15 21:49

About this skill

PHPUnit

PHPUnit is the standard unit testing framework for the PHP ecosystem. Examples include usage in Laravel, Symfony, and WordPress.

When to Use

  • PHP Standard Testing Framework: The official, universal testing framework for Laravel, Symfony, and modern PHP applications.
  • Unit and Integration Testing: Testing PHP domain models, services, controllers, and database interactions.
  • Data Providers for Parameterized Tests: Running tests against multi-dimensional test datasets using @dataProvider.
  • Mocking & Test Doubles: Generating mock objects and verifying invocation expectations natively without external libraries.

Quick Start

<?php
use PHPUnit\Framework\TestCase;

final class StackTest extends TestCase { public function testPushAndPop(): void { $stack = []; $this->assertEmpty($stack);

    array_push($stack, 'foo');
    $this->assertNotEmpty($stack);
    $this->assertEquals('foo', array_pop($stack));
}

}


## Core Concepts

### PHPUnit Test Case Structure

Extends `TestCase` and leverages modern PHP 8 attributes:

```php
<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProvider;

final class CurrencyConverterTest extends TestCase
{
    #[Test]
    #[DataProvider('currencyProvider')]
    public function it_converts_usd_to_eur(float $usd, float $expectedEur): void
    {
        $converter = new CurrencyConverter(exchangeRate: 0.92);
        $this->assertEqualsWithDelta($expectedEur, $converter->toEur($usd), 0.01);
    }

    public static function currencyProvider(): array
    {
        return [
            'zero value' => [0.0, 0.0],
            'standard transaction' => [100.0, 92.0],
            'fractional amount' => [10.50, 9.66],
        ];
    }
}

Native Mock Objects

Stubs methods and asserts on invocation parameters:

public function testPaymentServiceSendsNotification(): void
{
    $mailerMock = $this->createMock(MailerInterface::class);
    $mailerMock->expects($this->once())
        ->method('send')
        ->with($this->equalTo('user@example.com'));

    $service = new PaymentService($mailerMock);
    $service->processPayment('user@example.com', 50);
}

Declarative phpunit.xml Configuration

Configures test suites, environment variables, and coverage enforcement:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
         cacheDirectory=".phpunit.cache">
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
    </testsuites>
    <source>
        <include><directory>src</directory></include>
    </source>
</phpunit>

Common Patterns

Data Providers for Clean Test Matrices

Problem: Writing multiple separate test methods to validate email formatting regex rules.

Solution: Use PHPUnit Data Providers:

use PHPUnit\Framework\TestCase;

class ValidatorTest extends TestCase
{
    /**
     * @dataProvider emailProvider
     */
    public function testEmailValidation(string $email, bool $expected): void
    {
        $validator = new EmailValidator();
        $this->assertSame($expected, $validator->isValid($email));
    }

    public static function emailProvider(): array
    {
        return [
            ['test@example.com', true],
            ['invalid-email', false],
            ['user@sub.domain.org', true],
            ['', false],
        ];
    }
}

Best Practices

Do:

  • Adopt PHP 8 Attributes: Use #[Test] and #[DataProvider] instead of legacy docblock annotations (@test).
  • Use Static Data Providers: Ensure all data provider methods are declared as public static.
  • Run with --colors=always --testdox: Produce clean, human-readable test output in local terminals and CI.
  • Enforce Strict Types in Tests: Add declare(strict_types=1); at the top of all test files.

Don't:

  • Use @runInSeparateProcess unless strictly necessary: Process isolation adds severe performance overhead.
  • Use assertEquals on floats: Use assertEqualsWithDelta to avoid floating point precision failures.
  • Catch exceptions manually: Use $this->expectException(CustomException::class) to assert on thrown errors.

Troubleshooting

Error Cause Solution
Class 'PHPUnit\Framework\TestCase' not found Composer autoload missing or PHPUnit not installed via Composer. Run composer dump-autoload and execute via ./vendor/bin/phpunit.
Failed asserting that false is true Method assertion failed. Add custom failure message: $this->assertTrue($val, 'Custom message').
Risky Test: This test did not perform any assertions Test executed code without performing explicit $this->assert*. Add assertion or mark with @doesNotPerformAssertions annotation.

References