Skip to content

Stop the test suite from destroying a real database - #20

Merged
krishnareddypadala merged 1 commit into
masterfrom
fix/test-db-isolation
Jul 29, 2026
Merged

krishnareddypadala merged 1 commit into
masterfrom
fix/test-db-isolation

Conversation

@krishnareddypadala

Copy link
Copy Markdown
Owner

Running php artisan test inside the deployed container dropped every table in the live lab database and reported 64 passing tests.

Proven on the VM:

rows after reseed:         4
rows after running tests:  0

Cause

Most tests use RefreshDatabase, which drops and rebuilds every table on whatever connection it is handed. phpunit.xml declared DB_CONNECTION=sqlite — but Laravel resolves the real process environment first, and docker-compose exports DB_CONNECTION=mysql for the application. So the suite pointed at MySQL and did exactly what it was designed to do.

Why force="true" isn't the fix

I tried it first. Verified that an exported variable still wins over PHPUnit's forced <env>, because Laravel reads $_SERVER/getenv ahead of it. It's included anyway for environments where it does work, but it can't be relied on.

The actual fix

An interlock in Tests\TestCase::setUp() that runs before parent::setUp(), and therefore before RefreshDatabase can touch anything. It fails closed — the run aborts with an explanation and a reseed command rather than trusting the environment:

=========================================================================
 TEST RUN ABORTED -- this suite would have destroyed a real database.
=========================================================================

Plus TestIsolationTest as a second line of defence, asserting the connection really is in-memory SQLite.

Why this was worth fixing properly

The failure shape, not the bug itself. It was destructive and silent — nothing in the output suggested the suite had done anything other than pass. A note-to-self not to run tests in the container would have held right up until the next person did it.

Third bug found by deploying, after #19's two. All three shared a cause: they needed a real container to surface, and a dev machine structurally cannot reproduce them.

Suite is now 66 tests.

🤖 Generated with Claude Code

Running `php artisan test` inside the deployed container DROPPED EVERY
TABLE in the live lab database and reported 64 passing tests. Proven on
the VM: 4 seeded rows, run the suite, 0 rows.

Cause: most tests use RefreshDatabase, which drops and rebuilds every
table on whatever connection it is handed. phpunit.xml declared
DB_CONNECTION=sqlite, but Laravel resolves the REAL process environment
first, and docker-compose exports DB_CONNECTION=mysql for the
application. So the suite pointed at MySQL.

Adding force="true" in phpunit.xml is not sufficient -- verified that an
exported variable still wins. So the real fix is an interlock in
Tests\TestCase that runs BEFORE parent::setUp(), and therefore before
RefreshDatabase can touch anything. It fails closed: the run aborts with
an explanation and a reseed command instead of trusting the environment.

Also adds TestIsolationTest as a second line of defence, asserting the
connection really is in-memory SQLite, and forces the DB_ variables in
phpunit.xml anyway for the environments where that does work.

The failure shape is what made this worth fixing properly rather than
just remembering not to do it: destructive AND silent. Nothing in the
output suggested the suite had done anything other than pass.

Suite is now 66 tests.
@krishnareddypadala
krishnareddypadala merged commit d04b5d0 into master Jul 29, 2026
3 checks passed
@krishnareddypadala
krishnareddypadala deleted the fix/test-db-isolation branch July 29, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant