Setting Up Tests
How a Laravel test suite is wired: the tests directory, the testing environment, make:test, and the first green run.
php 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
<?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.
<?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.
<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.
<?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