When building a PHP application, we usually test it by opening the application in a browser and clicking around.
But there is another important kind of testing:
Testing the code itself.
This is where PHPUnit comes in.
PHPUnit is a testing framework for PHP that allows us to automatically check whether our code produces the results we expect.
The most important idea to understand is:
PHPUnit isn’t primarily about trying random values. It’s about automatically protecting your application’s logic from future changes.
What problem does PHPUnit solve?
Imagine you have an e-commerce application with this method:
class Order
{
public function calculateTotal($price, $quantity, $discount)
{
$total = $price * $quantity;
if ($discount > 0) {
$total -= $total * $discount / 100;
}
return $total;
}
}
You know that:
100 × 3 = 300
And with a 10% discount:
300 - 10% = 270
So you write a PHPUnit test:
public function testOrderWithDiscount()
{
$order = new Order();
$result = $order->calculateTotal(100, 3, 10);
$this->assertEquals(270, $result);
}
The test is basically saying:
“When I give this method 100, 3 and 10, I expect the answer to be 270.”
PHPUnit checks that automatically.
What happens when the code changes?
This is where PHPUnit becomes really useful.
Imagine six months later you modify calculateTotal() because you are adding tax calculations.
You accidentally break the discount calculation.
Without a test, you might not notice.
But with PHPUnit, you run:
./vendor/bin/phpunit
And PHPUnit might tell you:
Expected: 270
Actual: 300
Now you immediately know:
“My recent change broke an existing business rule.”
That’s the real value of unit testing.
PHPUnit is like a safety net
Think of your application as a building.
Every time you change the code, there is a possibility that you accidentally break something that was already working.
Your PHPUnit tests act like a safety net.
Existing code
↓
Write tests
↓
Change code
↓
Run PHPUnit
↓
Everything passes? ✅
Something broke? ❌
The more important business logic you test, the more confidence you have when changing your application.
What does assertEquals() mean?
You will see assertions everywhere in PHPUnit.
For example:
$this->assertEquals(270, $result);
It means:
“I expect
$resultto be equal to 270.”
Some other common assertions are:
$this->assertTrue($value);
$this->assertFalse($value);
$this->assertNull($value);
$this->assertNotNull($value);
$this->assertEquals(100, $value);
These are PHPUnit methods, not core PHP functions.
You don’t need to learn hundreds of methods to start. Learn the common assertions first.
A real application example
Suppose your application has a user registration rule:
function isValidAge($age)
{
return $age >= 18;
}
You might test:
public function testAdultUser()
{
$this->assertTrue(isValidAge(25));
}
And:
public function testUnderageUser()
{
$this->assertFalse(isValidAge(16));
}
Now imagine someone changes the logic later:
return $age > 21;
Your existing test will fail.
That’s good.
The test has caught a change that might have introduced a bug.
What about testing different values?
PHPUnit can test multiple scenarios using a data provider.
For example:
public static function ageProvider()
{
return [
[25, true],
[18, true],
[16, false],
[10, false],
];
}
Then:
#[DataProvider('ageProvider')]
public function testAge($age, $expected)
{
$this->assertEquals($expected, isValidAge($age));
}
PHPUnit runs the same test with each set of values.
You don’t have to manually run the method four times.
PHPUnit vs Tinker
This is an important difference.
Tinker
Tinker is mainly an interactive playground.
For example, in Laravel:
php artisan tinker
Then you can experiment:
$order->calculateTotal(100, 3, 10);
You might try:
$order->calculateTotal(200, 5, 20);
Then:
$order->calculateTotal(500, 2, 0);
This is useful when you want to manually explore your code.
It’s like saying:
“Let me try this value and see what happens.”
PHPUnit
PHPUnit is different.
You write the expected scenarios once:
100 × 3 with 10% discount → 270
200 × 5 with 20% discount → 800
500 × 2 with no discount → 1000
Then PHPUnit can check them automatically whenever you run your tests.
It’s like saying:
“These rules must always remain true.”
The simplest way to remember the difference
Tinker:
“Let me experiment with my code.”
PHPUnit:
“Let me prove that my code continues to work.”
PHPUnit isn’t only for complex applications
You don’t need a huge Laravel application to use PHPUnit.
Even a simple PHP application can benefit from it.
For example:
my-app/
│
├── src/
│ └── Calculator.php
│
├── tests/
│ └── CalculatorTest.php
│
└── composer.json
Your application code is in src/.
Your tests are in tests/.
You make a change to your application and then run:
./vendor/bin/phpunit
If everything is okay:
OK
If something is broken, PHPUnit tells you which test failed.
When should you write PHPUnit tests?
You don’t necessarily need a test for every tiny line of PHP.
Tests are particularly valuable for business logic.
For example:
- Calculating order totals
- Applying discounts
- Calculating taxes
- Checking user permissions
- Validating business rules
- Calculating salaries
- Determining shipping costs
- Processing payment amounts
- Converting data
- Handling important edge cases
For example:
Order total
↓
Discount
↓
Tax
↓
Shipping
↓
Final amount
If these calculations are important to your application, having tests around them is very useful.
The bigger picture
Imagine you have 50 important business rules in your application.
You write tests for those rules.
Later, you change some code.
Instead of manually checking all 50 scenarios through the browser, you can run:
./vendor/bin/phpunit
And PHPUnit checks them for you.
That’s why testing becomes increasingly valuable as an application grows.
The core idea
If you remember only one thing about PHPUnit, remember this:
PHPUnit lets you define what your code is supposed to do, and then automatically checks that those expectations still hold after you change your code.
Tinker is great for exploring.
PHPUnit is great for protecting.
And that’s why professional PHP applications use automated tests.
