Testing in Laravel can feel straightforward at first. You write a few tests, run php artisan test, see green output, and move on. But here’s the uncomfortable truth: many passing tests don’t actually protect your application.
If your tests don’t catch real bugs, they’re not just useless—they give you a false sense of confidence.
In this article, we’ll go through the most common Laravel testing mistakes that quietly break the value of your test suite, along with practical examples and better approaches.
You can be also interested in testing databse logic: https://codecraftdiary.com/2026/01/03/testing-database-logic-what-to-test-what-to-skip-and-why-it-matters/
1. Testing Implementation Instead of Behavior
One of the biggest mistakes is writing tests that mirror your code instead of validating what your application actually does.
Bad example
public function test_it_calls_service_method()
{
$service = Mockery::mock(UserService::class);
$service->shouldReceive('createUser')->once();
$controller = new UserController($service);
$controller->store(new Request([...]));
}
This test only checks that a method was called. It doesn’t verify:
- what was created
- whether the data is correct
- whether anything actually works
Better approach
public function test_user_is_created()
{
$response = $this->post('/users', [
'name' => 'John Doe',
'email' => 'john@example.com',
]);
$response->assertStatus(201);
$this->assertDatabaseHas('users', [
'email' => 'john@example.com',
]);
}
Focus on observable behavior, not internal calls.
2. Overusing Mocks
Mocks are powerful—but overusing them leads to fragile and meaningless tests.
Problem
When everything is mocked:
- you’re not testing real integration
- your tests pass even if the system is broken
Http::fake();
$response = $this->get('/weather');
$response->assertStatus(200);
This tells you nothing about:
- response structure
- data correctness
- edge cases
Better approach
Mock only what you must (external services), and assert meaningful output:
Http::fake([
'*' => Http::response(['temp' => 25], 200),
]);
$response = $this->get('/weather');
$response->assertJson([
'temperature' => 25,
]);
Rule of thumb:
Mock boundaries, not your own logic.
3. Writing Tests That Always Pass
Some tests are written in a way that they can’t fail—even if the code is broken.
Example
public function test_response_is_ok()
{
$response = $this->get('/users');
$response->assertStatus(200);
}
This test will pass even if:
- the response is empty
- the wrong data is returned
- business logic is broken
Better approach
$response->assertJsonStructure([
'data' => [
'*' => ['id', 'name', 'email']
]
]);
Or even better:
$this->assertDatabaseCount('users', 3);
Ask yourself:
“What bug would this test catch?”
If the answer is “none”, rewrite it.
4. Ignoring Edge Cases
Most bugs don’t happen in the “happy path”. They happen at the edges.
Common mistake
Only testing valid input:
$this->post('/users', [
'email' => 'john@example.com',
]);
Better approach
Test invalid scenarios:
$this->post('/users', [
'email' => 'not-an-email',
])->assertSessionHasErrors('email');
Also test:
- missing fields
- duplicate values
- unexpected input
Good tests try to break your application.
5. Testing Too Much in Unit Tests
Unit tests should be fast and focused. But many developers turn them into mini integration tests.
Example
public function test_order_creation()
{
$order = OrderService::create([...]);
$this->assertDatabaseHas('orders', [...]);
}
This mixes:
- business logic
- database layer
Better approach
Split responsibilities:
Unit test (logic only):
public function test_total_price_is_calculated_correctly()
{
$total = OrderCalculator::calculate([100, 200]);
$this->assertEquals(300, $total);
}
Feature test (full flow):
$this->post('/orders', [...]);
$this->assertDatabaseHas('orders', [...]);
Keep your test layers clean.
6. Not Using Factories Properly
Laravel factories are powerful, but many developers misuse them.
Problem
Hardcoding everything:
User::create([
'name' => 'Test',
'email' => 'test@example.com',
]);
Better approach
$user = User::factory()->create();
Even better:
$user = User::factory()->state([
'email_verified_at' => now(),
])->create();
Benefits:
- less boilerplate
- more flexible tests
- easier maintenance
7. Not Cleaning Up Test Data
Dirty test data can cause flaky tests.
Problem
Tests depend on previous state.
Solution
Use:
use Illuminate\Foundation\Testing\RefreshDatabase;
This ensures:
- clean DB for each test
- consistent results
Flaky tests destroy trust in your test suite.
8. Writing Tests That Are Too Complex
If your test is hard to read, it’s probably doing too much.
Example
public function test_everything()
{
// 50 lines of setup
// 10 assertions
}
Better approach
Break it down:
public function test_user_can_register() {}
public function test_email_must_be_unique() {}
public function test_password_is_required() {}
Each test should answer one question.
9. Ignoring Performance
Slow tests are often skipped—and skipped tests are useless tests.
Problem
- too many DB calls
- unnecessary setup
- heavy fixtures
Tips
- use in-memory database (SQLite)
- avoid unnecessary seeding
- keep unit tests fast
Fast tests = tests you actually run.
10. Not Testing Real User Flows
Testing isolated pieces is not enough.
Problem
You test services and controllers separately, but never the full flow.
Example
public function test_user_can_register_and_login()
{
$this->post('/register', [
'email' => 'john@example.com',
'password' => 'password',
]);
$this->post('/login', [
'email' => 'john@example.com',
'password' => 'password',
])->assertRedirect('/dashboard');
}
This is what actually matters:
Can the user complete the action?
Final Thoughts
Laravel makes testing easy—but writing useful tests is a different skill.
If your tests:
- only check status codes
- mock everything
- mirror your implementation
…then they’re not protecting your application.
Instead, focus on:
- real behavior
- meaningful assertions
- edge cases
- realistic user flows
A smaller set of high-quality tests is far more valuable than a large suite of weak ones.
Quick Checklist
Before committing a test, ask:
- Does this test fail if something important breaks?
- Am I testing behavior, not implementation?
- Would this catch a real bug?
- Is this test simple and readable?
If the answer is “no”, it’s time to improve it.
Well-written tests are not just about coverage—they’re about confidence. And confidence comes from tests that actually matter.
Top comments (0)