Introduction
Before Laravel 13 you had a binary choice: extend Tests\TestCase (app booted, slow, every helper available) or PHPUnit\Framework\TestCase (no app, fast, no helpers). Mixing styles forced you to split related tests across two files. Laravel 13 added the #[UnitTest] attribute so you can flag individual test methods as "skip the app boot just for me."
Key Concepts
- Class-level base: Determines the default mode — booted or not.
- Method-level attribute:
#[UnitTest]on a single method overrides the class default for that method only.
Real World Context
A ProductTest that has 8 feature tests hitting the product controller and 2 pure unit tests on a price-formatting helper. You want all 10 in one file for cohesion, but the 2 helper tests shouldn't pay the cost of booting Laravel.
Deep Dive
Mixing booted and non-booted tests
php<?php namespace Tests\Feature; use App\Services\PriceFormatter; use Illuminate\Foundation\Testing\Attributes\UnitTest; use Tests\TestCase; class ProductTest extends TestCase { // Normal feature test — Laravel is booted public function test_admin_can_list_products(): void { $admin = User::factory()->admin()->create(); $this->actingAs($admin) ->get('/admin/products') ->assertOk(); } // Pure unit test — app boot is skipped for this method #[UnitTest] public function test_price_formatter_rounds_to_two_decimals(): void { $formatter = new PriceFormatter(); $this->assertEquals('$9.99', $formatter->format(9.994)); } }
When PHPUnit runs test_admin_can_list_products, Laravel's CreatesApplication trait boots the container. When it runs test_price_formatter_rounds_to_two_decimals, the attribute tells the runner to skip that bootstrap — so $this->app, actingAs(), and every other helper that relies on the container are unavailable inside that method.
What you lose inside a #[UnitTest] method
Inside an attribute-marked method you cannot use:
$this->get(),$this->post(), and other HTTP helpers.$this->app,$this->actingAs().- Eloquent models (they need the connection from the container).
- Facades (they resolve through the container).
You can still use:
- Pure PHP code.
- Classes instantiated directly with
new. - Mockery directly.
- Data providers.
When to reach for it
Use #[UnitTest] when a test is genuinely pure and you want to colocate it with related feature tests for readability. If a whole file is pure, just extend PHPUnit\Framework\TestCase — the attribute is for mixed files.
Common Pitfalls
- Using Eloquent inside a
#[UnitTest]method — models need the DB connection resolved by the container. The test throws a confusing "no connection" error. - Applying the attribute to setUp/tearDown — it only applies to test methods. Setup still runs under the class's base mode.
Best Practices
- Keep
#[UnitTest]methods pure — if you find yourself wanting a facade, delete the attribute and let the app boot. - Group
#[UnitTest]methods at the bottom of the class — visual separation makes it obvious which section has which capabilities.
Summary
#[UnitTest]is a Laravel 13 attribute that skips app boot for a single test method.- Lives in
Illuminate\Foundation\Testing\Attributes. - Use it to colocate pure-logic tests with their feature-test neighbors without slowing the whole suite.