Setting Up Tests

How a Laravel test suite is wired: the tests directory, the testing environment, make:test, and the first green run.

Syntaxphp artisan make:test HomePageTest [--unit] [--pest|--phpunit]

A test is a PHP method that exercises your application and fails loudly when the behaviour changes. Laravel 13 ships the whole rig: a tests directory, a phpunit.xml, and a base Tests\TestCase that boots the application for each test method.

Two directories

tests/Unit holds tests that do not boot the application, so they cannot reach the database or any framework service. tests/Feature holds tests that do boot it and can send a request to a route. Most of your tests belong in Feature, because that is where the wiring lives.

Pest or PHPUnit

The Laravel 13 skeleton requires phpunit/phpunit in composer.json. Pest is an optional layer on top of PHPUnit: installing pestphp/pest lets you write test('name', function () { ... }) in place of a class, and adds a tests/Pest.php for suite-wide setup. The assertions are identical. php artisan test runs whichever you have and is the same run as ./vendor/bin/phpunit or ./vendor/bin/pest, with a more readable report.

The testing environment

The <env> entries in phpunit.xml are what make the suite safe: APP_ENV=testing, CACHE_STORE and SESSION_DRIVER set to array, QUEUE_CONNECTION=sync, MAIL_MAILER=array, and DB_CONNECTION=sqlite with DB_DATABASE=:memory:. A .env.testing file, when present, is used instead of .env for the run.

Generate a test with php artisan make:test HomePageTest. It lands in tests/Feature; --unit puts it in tests/Unit, and --pest or --phpunit forces the style.

Example

Example · php
<?php
// tests/Feature/HomePageTest.php

declare(strict_types=1);

namespace Tests\Feature;

use Tests\TestCase;

class HomePageTest extends TestCase
{
    public function test_the_home_page_loads(): void
    {
        $response = $this->get('/');

        // assertOk() is the same assertion as assertStatus(200).
        $response->assertOk();
        $response->assertSee('Welcome');
    }

    public function test_an_unknown_page_returns_404(): void
    {
        // assertNotFound() is the same assertion as assertStatus(404).
        $this->get('/no-such-page')->assertNotFound();
    }
}

When to use it

  • Add the first regression test to an application that has none.
  • Run only the file you are working on with php artisan test --filter=HomePageTest.
  • Point the suite at a scratch MySQL database by editing .env.testing instead of phpunit.xml.
  • Decide between Pest and PHPUnit for a new project and know exactly what changes.

More examples

The same two tests in Pest

Pest's test() registers a closure against the same Tests\TestCase, which is why $this->get() still works inside it. Reach for Pest when you want less ceremony per test; the class form is better when several tests share setUp.

Example · php
<?php
// tests/Feature/HomePageTest.php

test('the home page loads', function () {
    $this->get('/')->assertOk()->assertSee('Welcome');
});

test('an unknown page returns 404', function () {
    $this->get('/no-such-page')->assertNotFound();
});

What phpunit.xml sets for the run

This block, from the Laravel 13 skeleton, is why tests do not send mail, do not persist sessions and do not need a database server. Edit it (or .env.testing) when you need the suite to run against MySQL or Postgres instead of in-memory SQLite.

Example · xml
<php>
    <env name="APP_ENV" value="testing"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
    <env name="CACHE_STORE" value="array"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
    <env name="MAIL_MAILER" value="array"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
</php>

Skipping the application boot for one method

Laravel 13 adds the #[UnitTest] attribute. Use it when one method in an otherwise framework-dependent class is pure logic and you do not want to pay for the boot.

Example · php
<?php
// tests/Feature/LocationServiceTest.php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\Attributes\UnitTest;
use Tests\TestCase;

class LocationServiceTest extends TestCase
{
    public function test_get_coordinates_resolves_address(): void
    {
        // Uses the application: container, config, HTTP client.
    }

    #[UnitTest]
    public function test_get_state_returns_state_from_abbreviation(): void
    {
        // Runs without booting the application.
    }
}

Discussion

  • Be the first to comment on this lesson.