Testing in PHP: Unit, Integration, E2E from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why is testing needed in PHP?

\n\n

Developing in PHP without testing is like building a house without a foundation. Sooner or later, the application will crash, and users will see the "white screen of death." Code testing is not a luxury, but a necessity for any serious project. It allows you to:

\n
    \n
  • Detect errors at early stages — before the code reaches production.
  • \n
  • Simplify refactoring — you can confidently change the architecture, knowing that tests will catch regressions.
  • \n
  • Create self-documenting code — tests serve as live documentation of how the system should work.
  • \n
  • Increase team confidence — every new commit undergoes automatic verification.
  • \n
\n\n

In PHP, there are three main levels of testing: Unit, Integration, and E2E (End-to-End). In this article, we will break down each of them with practical examples, from simple asserts to full-fledged browser tests.

\n\n

1. Unit Testing: Testing the "Building Blocks" of the Application

\n\n

What is a Unit Test?

\n

A unit test checks the operation of one small block of code — typically one method or function — in complete isolation from external dependencies (databases, APIs, file systems).

\n\n

Tools for PHP

\n
    \n
  • PHPUnit — the de facto standard. Used in Laravel, Symfony, and many CMS platforms.
  • \n
  • Pest — a modern alternative with a more concise syntax, built on top of PHPUnit.
  • \n
\n\n

Example: Testing a Discount Calculator

\n

Suppose we have a simple class DiscountCalculator:

\n\n
<?php\n\nclass DiscountCalculator\n{\n    public function calculate(float $price, float $percent): float\n    {\n        if ($percent < 0 || $percent > 100) {\n            throw new \\InvalidArgumentException('Percent must be between 0 and 100');\n        }\n        return $price - ($price * $percent / 100);\n    }\n}\n
\n\n

Now let's write a Unit test using PHPUnit:

\n\n
<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\nclass DiscountCalculatorTest extends TestCase\n{\n    private DiscountCalculator $calculator;\n\n    protected function setUp(): void\n    {\n        $this->calculator = new DiscountCalculator();\n    }\n\n    public function testCalculateWithZeroPercent(): void\n    {\n        $result = $this->calculator->calculate(100, 0);\n        $this->assertEquals(100, $result);\n    }\n\n    public function testCalculateWithFiftyPercent(): void\n    {\n        $result = $this->calculator->calculate(200, 50);\n        $this->assertEquals(100, $result);\n    }\n\n    public function testCalculateThrowsExceptionOnInvalidPercent(): void\n    {\n        $this->expectException(\\InvalidArgumentException::class);\n        $this->calculator->calculate(100, 150);\n    }\n}\n
\n\n

Key points:

\n
    \n
  • Each method tests only one scenario.
  • \n
  • We do not access the database or external services.
  • \n
  • We use setUp() to initialize common objects.
  • \n
\n\n

Best Practices for Unit Testing

\n
    \n
  • Follow the FIRST principle: Fast, Isolated, Repeatable, Self-validating, Timely.
  • \n
  • Name tests as "test_action_expectedResult".
  • \n
  • Avoid logic in tests — no if/switch/loops.
  • \n
\n\n

2. Integration Testing: Checking Component Interaction

\n\n

What is the difference from Unit?

\n

Integration tests check how different parts of the system work together. For example, how your service interacts with a real database, cache, or external API.

\n\n

Example: Testing a Repository with a Database (Laravel + SQLite)

\n

Suppose we have a <

Blogs

Book Recommendations