laravel/laravel
Testing
Practical patterns for writing tests against this skeleton. The structural details (suites, base classes, env overrides) are documented in Systems → Tests; this page covers the common workflows you'll actually run.
Run the full suite
composer test # clears cached config, runs php artisan test
php artisan test # same, without the config:clear pre-stepcomposer test chains php artisan config:clear --ansi @no_additional_args && php artisan test. The @no_additional_args directive (added in v13.4.0, PR #6799) prevents extra CLI arguments — like --filter=Foo — from leaking into the config:clear invocation.
If you want to pass arguments through, run php artisan test directly:
php artisan test --filter=ExampleTest
php artisan test --testsuite=Unit
php artisan test --testsuite=Feature
php artisan test --parallel
php artisan test --coverage
php artisan test --coverage --min=80 # fail if coverage drops below 80%Choose the right base class
// Feature test — boots the framework
namespace Tests\Feature;
use Tests\TestCase;
class ProfileTest extends TestCase
{
public function test_user_can_view_their_profile(): void
{
$user = \App\Models\User::factory()->create();
$this
->actingAs($user)
->get('/profile')
->assertOk();
}
}// Unit test — does NOT boot the framework
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class StringHelperTest extends TestCase
{
public function test_slug_lowercases_input(): void
{
$this->assertSame('hello-world', slugify('Hello World'));
}
}The two base classes give different capabilities:
Tests\TestCase(extendsIlluminate\Foundation\Testing\TestCase) → HTTP helpers, container, config, DB.PHPUnit\Framework\TestCase→ just PHPUnit. Faster, but you can't use$this->get(...),Hash::*,Cache::*, etc.
If you need to test a class that depends on the container, it goes in Feature/ — even if it's not testing an HTTP endpoint.
Database tests
The phpunit env (phpunit.xml) sets DB_CONNECTION=sqlite and DB_DATABASE=:memory:. Apply the RefreshDatabase trait so each test runs in its own transaction:
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
public function test_user_email_must_be_unique(): void
{
User::factory()->create(['email' => 'dup@example.com']);
$this->expectException(\Illuminate\Database\UniqueConstraintViolationException::class);
User::factory()->create(['email' => 'dup@example.com']);
}
}RefreshDatabase runs migrations once per test process and wraps each test in a transaction that's rolled back at the end. Slow tests can use DatabaseMigrations (drops and re-runs migrations per test) instead, but RefreshDatabase is the default for most cases.
Common fakes
use Illuminate\Support\Facades\{Mail, Queue, Storage, Event, Bus, Notification};
Mail::fake(); // intercept mail; assert with Mail::assertSent(WelcomeMail::class)
Queue::fake(); // intercept queued jobs; Queue::assertPushed(MyJob::class)
Bus::fake(); // intercept dispatched jobs (incl. chains/batches)
Storage::fake('local'); // route Storage::disk('local') to an in-memory FS
Event::fake(); // intercept events; Event::assertDispatched(UserCreated::class)
Notification::fake(); // intercept notificationsApply the fake before the action under test. Most assertions accept a closure for fine-grained matching:
Mail::assertSent(WelcomeMail::class, fn ($mail) => $mail->user->id === $user->id);Using the seeder
tests/TestCase.php doesn't auto-seed, but you can call the seeder explicitly:
$this->seed(); // runs DatabaseSeeder::run()
$this->seed(\Database\Seeders\PostsSeeder::class); // runs a specific seederRefreshDatabase resets the DB between tests, so seeded rows don't leak.
Handling auth
$user = \App\Models\User::factory()->create();
$this
->actingAs($user)
->get('/dashboard')
->assertOk();actingAs works for the default web guard. For other guards, pass the guard name as the second argument:
$this->actingAs($apiUser, 'api');Asserting response shape
$response = $this->getJson('/api/users');
$response
->assertOk()
->assertJsonCount(3, 'data')
->assertJsonStructure(['data' => [['id', 'name', 'email']]])
->assertJsonPath('data.0.email', 'first@example.com');For HTML pages:
$this->get('/')
->assertSeeText('Let\'s get started')
->assertViewIs('welcome');Handling artisan commands
$this->artisan('inspire')
->assertSuccessful()
->expectsOutputToContain('—'); // an em-dash in any quote attributionFor interactive commands, chain ->expectsQuestion('What is your name?', 'Alice') and ->expectsConfirmation(...) to script the conversation.
Parallel tests
php artisan test --parallel --processes=4This requires brianium/paratest in dev (it's not in the skeleton's composer.json by default — install it explicitly if you want this). Each worker gets its own SQLite DB; the framework auto-suffixes DB_DATABASE per process. Tests that mutate global filesystem state need careful handling.
Coverage
# Requires Xdebug or PCOV
XDEBUG_MODE=coverage php artisan test --coveragephpunit.xml's <source> block scopes coverage to app/ only — so the 1,300 lines of config don't dilute your numbers. The skeleton's app/ directory has 64 LOC; once you add controllers and services this becomes meaningful.
CI parity
CI (.github/workflows/tests.yml) does not run coverage and does not run Pint. It runs:
composer install --prefer-dist --no-interaction --no-progresscp .env.example .envphp artisan key:generatephp artisan test
Across PHP 8.3, 8.4, 8.5. To match CI locally:
rm -rf vendor
composer install --prefer-dist --no-interaction --no-progress
cp .env.example .env.test
php artisan key:generate --env=test
php artisan testCommon gotchas
- Tests pass locally, fail in CI. Almost always a missing PHP extension on your local machine that CI has. Look at the workflow's
extensions:list (dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite). - Cached config breaks tests.
composer testrunsphp artisan config:clearfirst for this reason. If you skip it and you've previously runphp artisan config:cache, env overrides fromphpunit.xmlmay be ignored. DB_URLset in your shell. Thephpunit.xmlexplicitly sets<env name="DB_URL" value=""/>to neutralize this — but only if PHPUnit is the env owner. Some IDEs propagate shell env vars; if your tests are talking to a real DB, check forDB_URLin your env (echo $DB_URL).
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.