DEV Community

Cover image for Laravel Testing Mistakes That Make Your Tests Useless
CodeCraft Diary
CodeCraft Diary

Posted on • Originally published at codecraftdiary.com

Laravel Testing Mistakes That Make Your Tests Useless

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([...]));
}
Enter fullscreen mode Exit fullscreen mode

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',
    ]);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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,
]);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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']
    ]
]);
Enter fullscreen mode Exit fullscreen mode

Or even better:

$this->assertDatabaseCount('users', 3);
Enter fullscreen mode Exit fullscreen mode

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',
]);
Enter fullscreen mode Exit fullscreen mode

Better approach

Test invalid scenarios:

$this->post('/users', [
    'email' => 'not-an-email',
])->assertSessionHasErrors('email');
Enter fullscreen mode Exit fullscreen mode

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', [...]);
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

Feature test (full flow):

$this->post('/orders', [...]);

$this->assertDatabaseHas('orders', [...]);
Enter fullscreen mode Exit fullscreen mode

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',
]);
Enter fullscreen mode Exit fullscreen mode

Better approach

$user = User::factory()->create();
Enter fullscreen mode Exit fullscreen mode

Even better:

$user = User::factory()->state([
    'email_verified_at' => now(),
])->create();
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Better approach

Break it down:

public function test_user_can_register() {}
public function test_email_must_be_unique() {}
public function test_password_is_required() {}
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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)