# Writing My First Tests with PHPUnit

> From installing PHPUnit to writing my first unit tests — what worked, what tripped me up, and the lessons learned along the way.

- Published: 2018-06-03
- Category: Tools & Technologies
- Tags: PHP, Testing
- Reading time: 3 min read
- Source: https://www.muhammetsafak.com.tr/en/blog/writing-my-first-tests-with-phpunit/
- Language: en-US
- Author: Muhammet Şafak

---
I put off writing tests for a long time. The phrase "I'll add tests once the project is done" sat in the back of my mind for years — and that "once" never came. This year I did things differently: I sat down and actually wrote tests with PHPUnit for a small feature. This post is about that experience — not theory, but what I did in practice.

## Installing PHPUnit

If you're using Laravel, PHPUnit already ships under the `require-dev` section of [`composer.json`](/en/blog/switching-to-composer-stop-managing-php-dependencies-by-hand/). To add it to a standalone project:

```bash
composer require --dev phpunit/phpunit
```

Creating a `phpunit.xml` configuration file at the project root is a good habit. Laravel provides this file by default; in other projects, the bare minimum looks like this:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>
```

## My First Unit Test

A **unit test** means testing a single class or method in isolation. You disable external dependencies, the database, and third-party services, then ask one question: does this method behave correctly on its own?

I started with a price calculation class. It's simple: it takes an amount, adds tax, and returns the total price.

```php
<?php

namespace App\Services;

class PriceCalculator
{
    public function withTax(float $price, float $taxRate = 0.18): float
    {
        return round($price * (1 + $taxRate), 2);
    }
}
```

The test for this class:

```php
<?php

namespace Tests\Unit;

use App\Services\PriceCalculator;
use PHPUnit\Framework\TestCase;

class PriceCalculatorTest extends TestCase
{
    public function test_withTax_returns_correct_total()
    {
        $calculator = new PriceCalculator();

        $result = $calculator->withTax(100.00);

        $this->assertEquals(118.00, $result);
    }

    public function test_withTax_applies_custom_rate()
    {
        $calculator = new PriceCalculator();

        $result = $calculator->withTax(200.00, 0.08);

        $this->assertEquals(216.00, $result);
    }
}
```

To run the test:

```bash
./vendor/bin/phpunit tests/Unit/PriceCalculatorTest.php
```

Seeing the green output gave me a small but tangible sense of satisfaction. Not just knowing "this code works" — but knowing "there is something that proves this code works."

## Things I Noticed While Writing Tests

After the first few tests, I realized something: writing tests isn't hard, but writing testable code requires deliberate thought.

Early on, I used to pack database queries, file reads, and business logic all into the same method. When I wanted to test it, it was impossible without a database. That was a dividing line: if I can't test a method in isolation, that method is probably carrying too many responsibilities.

Writing the tests forced me to gradually clean up the code.

## Laravel Feature Tests

Beyond unit tests, Laravel makes it easy to write **feature tests** as well. These tests simulate an HTTP request, letting you verify that routes, controllers, and middleware all work together — without a real browser.

```php
<?php

namespace Tests\Feature;

use App\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class OrderTest extends TestCase
{
    use RefreshDatabase;

    public function test_authenticated_user_can_create_order()
    {
        $user = factory(User::class)->create();

        $response = $this->actingAs($user)
            ->postJson('/api/orders', [
                'product_id' => 1,
                'quantity'   => 2,
            ]);

        $response->assertStatus(201);
        $this->assertDatabaseHas('orders', ['user_id' => $user->id]);
    }
}
```

The `RefreshDatabase` trait resets the database before each test, so tests don't bleed into one another. With `actingAs` I can simulate a logged-in user. This infrastructure keeps test writing practical even under complex conditions.

## Choosing What to Test

You don't need to test everything — and you can't. What matters early on is deciding what's important. My current focus areas are:

- Service classes that contain business logic
- Pure functions: calculations, transformations, filtering
- API endpoints — especially those that must accept or reject specific data

Testing trivial methods like getters and setters, or simple output, is a waste of time. I prioritize logic where a mistake has a high cost.

## Conclusion

Writing your first tests isn't a steep learning curve. The hard part is turning it into a habit. I started small: I added tests to an existing service class, stopped there, then added one more. Once the rhythm of writing tests set in, I started asking "can I test this?" while writing code too. That question, in turn, shapes the code for the better.
