Introduction: why test PHP applications?
Code testing is not just a trendy fad, but a basic practice of professional development. In the world of PHP, where a huge number of projects work with databases, external APIs and complex business logic, code quality directly affects the stability and security of the product. Without tests, even a small change can break functionality that has been working for years.
In this article, we will analyze three key levels of testing: unit, integration and e2e (end-to-end). You will learn how they differ, when to apply each of them, and see real code examples in PHP. Regardless of whether you use Laravel, Symfony or write in pure PHP, this knowledge will help you create reliable applications.
Unit testing: checking isolated components
Unit tests are the lowest and fastest level of testing. Their goal is to check the operation of an individual method or function in complete isolation from external dependencies (databases, file systems, HTTP requests).
Tools for unit testing in PHP
- PHPUnit — the de facto standard. Powerful, flexible, supported by most frameworks.
- Pest — a modern alternative with a more concise syntax. Perfect for beginners and teams who value readability.
Example using PHPUnit
Suppose we have a simple class for calculating a discount:
<?phpclass DiscountCalculator{ public function calculate(float $price, float $percent): float { if ($percent < 0 || $percent > 100) { throw new \\InvalidArgumentException('Discount percentage must be from 0 to 100'); } return $price - ($price * $percent / 100); }}?>The test for this class will look like this:
<?phpuse PHPUnit\\Framework\\TestCase;
class DiscountCalculatorTest extends TestCase{ public function testCalculateWithValidDiscount(): void { $calculator = new DiscountCalculator(); $result = $calculator->calculate(1000, 20); $this->assertEquals(800.0, $result); }
public function testCalculateWithZeroPercent(): void { $calculator = new DiscountCalculator(); $result = $calculator->calculate(500, 0); $this->assertEquals(500.0, $result); }
public function testCalculateThrowsExceptionForInvalidPercent(): void { $this->expectException(\\InvalidArgumentException::class); $calculator = new DiscountCalculator(); $calculator->calculate(100, 150); }}?>Key principles of unit tests:
- One test — one check (one assert).
- Tests should not depend on each other.
- Use mocks to isolate external services. For example, using
createMock()in PHPUnit. - Cover edge cases: null, empty strings, negative values.
Integration testing: checking component interaction
Integration tests check how different parts of the application work together: classes, databases, external services. Unlike unit tests, here we use real (or almost real) dependencies.
When to write integration tests?
- Checking interaction with the database (ORM, queries).
- Working with the file system (uploading/reading files).
- Interaction with external APIs (via test stubs).
- Checking the correctness of repositories and the service layer.
Example in PHP using PDO (without a framework)
<?phpuse PHPUnit\\Framework\\TestCase;
class User