How Mutation Testing Improved The Quality Of Our Unit Tests Of Practicesoftwaretesting.com

24-04-2025 door Roy de Kleijn

The codebase of practicesoftwaretesting.com includes many unit tests with high code coverage. But high coverage alone doesn’t guarantee quality. Some tests pass simply because they’re written to, not because they truly validate behavior.

To uncover those weak spots, we introduced mutation testing.

We also switched from PHPUnit to Pest, which gave us cleaner syntax and a better developer experience. Once Pest was in place, we integrated mutation testing using Pest Mutation Testing.

Mutation testing works by automatically changing your code (mutating it) and checking if your test suite catches the change. If it doesn’t, that’s a sign the test isn't doing its job.

Here are three concrete ways mutation testing helped us spot weak or misleading tests:


1. Catching Untested Logic in FavoriteController@destroy

Mutation:

- $this->favoriteService->deleteFavorite($id);
+ // removed

Our test was still passing, even though the deletion logic was gone. That revealed a big hole: our test wasn’t actually checking that the favorite was deleted.

We fixed it by adding an assertion that checks the database:

$this->assertDatabaseMissing('favorites', ['id' => $favorite->id]);

If the deletion ever goes missing again, the test will fail.


2. Verifying Auth Middleware in FavoriteController

Mutation:

- $this->middleware('auth:users');
+ // removed

Again, the tests passed—even when the authentication guard was removed. That means the endpoint could have become public without anyone noticing.

We added a new test to ensure unauthenticated users get rejected:

$this->deleteJson("/favorites/{$favorite->id}")
     ->assertStatus(401);

It’s simple, but it now ensures that route access is actually protected.


3. Boolean Cast in BrandController@update

Mutation:

- 'success' => (bool) $updated
+ 'success' => $updated

The mutation removed the cast to boolean, but the tests didn’t fail. That meant they weren't checking the response format closely enough.

We updated the test to assert the exact value:

$response->assertExactJson([
    'success' => true,
]);

This kind of detail matters when you return structured responses and want predictable behavior across clients.


Mutation testing revealed where our test suite was giving us a false sense of confidence. It surfaced tests that were green on the surface but ineffective in practice. Combining Pest with mutation testing gave us stronger assurance that our tests are catching real issues, not just giving code a free pass.

We didn’t start from scratch—we simply sharpened what we already had.

And that’s the kind of improvement that really counts.

Delen: