Master PHP Unit Testing with PHPUnit: Essential Guide & Best Practices

Unlock the power of PHPUnit in PHP development with our comprehensive guide on unit testing and best practices.

Master PHP Unit Testing with PHPUnit: Essential Guide & Best Practices

Learn PHP unit testing using PHPUnit. Discover testing strategies, mocks, coverage tools, and CI integration for properly working PHP codebases.

Unit tests help developers expose bugs early and keep code quality intact over time. This article discusses PHP unit testing through PHPUnit, the best‑known PHP testing framework. You will learn how to write real tests, organise your test suite, create mocks for dependencies, and add CI pipelines such as GitHub Actions.

Installing PHPUnit

Testing a unit guarantees that every aspect of a code element behaves as expected.

Composer install command


composer require --dev phpunit/phpunit
  

Initialise a test suite


./vendor/bin/phpunit --init
  

This generates the phpunit.xml configuration file and the tests/ directory.

You can run all tests with:


./vendor/bin/phpunit
  

Writing Your First PHPUnit Test

Let’s say you have a simple class:


class MathEngine {
    public function sum($num1, $num2) {
        return $num1 + $num2;
    }
}
  

Now create a test:


// tests/MathEngineTest.php
use PHPUnit\Framework\TestCase;

class MathEngineTest extends TestCase {
    public function testSumOperation() {
        $engine = new MathEngine();
        $this->assertEquals(4, $engine->sum(2, 2));
    }
}
  

Use assertions like assertEquals, assertTrue, assertFalse, assertInstanceOf, etc.

PHPUnit Test Structure and Best Practices

Arrange–Act–Assert Pattern

  1. Arrange: Set up objects and dependencies
  2. Act: Call the method you’re testing
  3. Assert: Verify the result

public function testZeroDivisionRaisesError() {
    $engine = new MathEngine();
    $this->expectException(DivisionByZeroError::class);

    $engine->divide(10, 0);
}
  

Group Related Tests into Classes

Group tests logically by class or module, e.g.:

Keep each test class focused on one unit.

Mocking and Stubs in PHPUnit

When testing units in isolation, you often need to mock dependencies that would otherwise trigger side effects (DB, API, mail, etc.).

Creating a mock


$fakeNotifier = $this->createMock(Notifier::class);
$fakeNotifier->expects($this->once())
             ->method('dispatch')
             ->with('member@example.com');
  

Inject this mock into the class being tested:


$notifier = new UserNotifier($mockMailer);
$notifier->sendWelcomeEmail('user@example.com');
  

Code Coverage with PHPUnit

Install Xdebug or PCOV and run:


./vendor/bin/phpunit --coverage-html coverage/
  

This generates a visual report in the coverage/ directory showing tested vs. untested lines.
Note: 100 % coverage doesn’t guarantee bug‑free code, but low coverage usually signals trouble.

Test‑Driven Development (TDD) in PHP

TDD is the practice of writing a test before writing the code it tests. The cycle is:

  1. Red: Write a failing test
  2. Green: Write minimal code
  3. Refactor: Clean up both the test and the code

TDD keeps your design minimal and focused on actual needs.


public function testLinkFormatter() {
    $formatter = new UrlSlugMaker();
    $this->assertEquals('hello-world', $formatter->makeSlug('Hello World'));
}

class UrlSlugMaker {
    public function makeSlug(string $text): string {
        return strtolower(str_replace(' ', '-', $text));
    }
}
  

Testing Exceptions and Edge Cases

Example: testing exceptions


$this->expectException(InvalidArgumentException::class);
$parser->parse(null);
  

You can also test:

Organising Your Test Suite

Best practices

Example structure


/src
  /Service
    UserService.php

/tests
  /Service
    UserServiceTest.php
  

Integrating PHPUnit with GitHub Actions

You can automatically run your PHP unit tests on every push or PR using GitHub Actions.

Example workflow .github/workflows/php.yml


name: Run PHPUnit Tests

on: [push, pull_request]

jobs:
  Test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          extensions: mbstring, xdebug
          tools: composer
      - run: composer install --no-progress
      - run: vendor/bin/phpunit
  

This CI setup ensures tests run automatically, reducing the risk of bugs being merged.

PHPUnit in Laravel and Symfony

Laravel Testing

Laravel uses PHPUnit by default and adds helpers:


$this->get('/api/user')->assertStatus(200);
  

You can use factories, database transactions, and Laravel’s artisan test runner.

Symfony Testing

Symfony also uses PHPUnit, with built‑in integration and testing environments:


$client = static::createClient();
$client->request('GET', '/blog');
$this->assertResponseIsSuccessful();
  

Use:

  1. KernelTestCase
  2. ApiTestCase
  3. WebTestCase

PHP unit testing is one practice every developer should embrace. With the PHPUnit framework, you can test business logic and prevent regressions across the code base. So start slow, test a lot, and let your tests guide your design.

Common Pitfalls to Avoid

Random 3 articles