Writing Laravel Tests That Are Worth Maintaining
A test suite that breaks every time you rename something is a liability. Here is what I test, what I deliberately do not, and why the ratio matters.
I have inherited projects with 90% coverage and constant production bugs, and projects with thirty tests that almost never break. The difference is what got tested, not how much.
Feature Tests Give the Best Return
A feature test exercises a real route through real middleware against a real database. It catches routing mistakes, validation gaps, authorization holes and broken queries — all in one test.
public function test_guest_cannot_reach_admin_blog(): void
{
$this->get('/admin/blog')->assertRedirect('/admin/login');
}
public function test_admin_can_publish_a_post(): void
{
$this->actingAs(User::factory()->admin()->create())
->post('/admin/blog', [
'title' => 'Test Post',
'excerpt' => 'Short summary.',
'body' => '<p>Body</p>',
'is_active' => 1,
])
->assertRedirect();
$this->assertDatabaseHas('blog_posts', ['slug' => 'test-post']);
}
Test Behaviour, Not Implementation
Assert that the post was created and the user was redirected. Do not assert that a particular private method was called. The first survives refactoring; the second is the reason people abandon test suites.
Where Unit Tests Earn Their Place
Pure logic with lots of branches: pricing calculations, date maths, permission rules, anything with edge cases you can enumerate. Those are cheap to test in isolation and genuinely worth it.
Use Factories, Not Fixtures
BlogPost::factory()->count(30)->create(['is_active' => true]);
Factories keep tests readable and let you generate the volume you need to catch performance problems.
The Flows That Must Have Tests
- Authentication and authorization boundaries.
- Anything that takes money.
- Anything that sends an email or fires a webhook.
- Every bug you have already fixed once.
That last one is the highest-value test in any suite. A bug that recurred is a bug that never had a test.
Run Them Automatically
Tests you have to remember to run are tests that stop being run. Wire them into CI on every push.