SkillByAIOpen interactive version →

Lesson 21 / 25

Testing and Code Quality: PHPUnit, Pest, PHPStan and Rector

Test PHP code and automate quality checks and upgrades.

A quality toolchain

PHPUnit is the standard testing framework: test classes extend TestCase, methods are marked with the #[Test] attribute or a test prefix, and assertions such as assertSame (strict) check results; data providers (#[DataProvider]) run one test with many inputs, and test doubles (createMock, createStub) replace collaborators. Pest offers a more concise, expressive syntax on top of PHPUnit and is popular in the Laravel community. Frameworks add HTTP testing helpers and database refreshing. Complement tests with static analysis (PHPStan or Psalm), coding style tools (PHP-CS-Fixer), and Rector, which applies automated refactorings and version upgrades (for example converting code to use constructor promotion, readonly properties or newer syntax). Infection provides mutation testing. Run everything in CI on every pull request: composer validate, composer audit, coding style, PHPStan, tests with coverage. Together they make large PHP codebases maintainable and upgrades between PHP versions routine rather than risky.

A PHPUnit test with a data provider

Strict assertions and one test method covering several cases.

<?php
declare(strict_types=1);

namespace Tests\Unit;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Shop\Shipping\ShippingCalculator;

final class ShippingCalculatorTest extends TestCase
{
    public static function cases(): iterable
    {
        yield 'below threshold, standard' => [99_999, false, 4_900];
        yield 'below threshold, express'  => [99_999, true, 9_900];
        yield 'at threshold is free'      => [100_000, true, 0];
    }

    #[Test]
    #[DataProvider('cases')]
    public function feeDependsOnTotalAndSpeed(int $totalPaise, bool $express, int $expected): void
    {
        $calculator = new ShippingCalculator();
        self::assertSame($expected, $calculator->feePaise($totalPaise, $express));
    }
}

// vendor/bin/phpunit
// vendor/bin/phpstan analyse --level=8
// vendor/bin/rector process src --dry-run      (preview automated upgrades)

assertSame over assertEquals

assertEquals compares loosely, so "1" equals 1. assertSame checks type and value, matching PHP's === and catching type bugs.

Quick check: What is Rector used for?

  • Serving HTTP requests
  • Database migrations
  • Automated refactoring and upgrading of PHP code
  • Caching opcodes
Answer

Automated refactoring and upgrading of PHP code — Rector rewrites code automatically, for example to adopt newer PHP syntax.