osmianski/fast-refresh-database
Fast Refresh Database is a Laravel package that speeds up test suites by replacing repeated migrations with a faster database reset strategy, keeping tests isolated and reducing runtime while supporting parallel testing.
This is a fork of plannr/laravel-fast-refresh-database (Sam Carré's original), re-tagged as 2.0.0 because v1.2.0 and below belong to the upstream history. The idea is unchanged: stop re-running migrate:fresh for every test class. Instead, migrate once, snapshot the result, and restore that snapshot for subsequent runs — restoring is dramatically cheaper than replaying migrations.
Minimal path:
php artisan vendor:publish with the tag listed in the README) and confirm where it stores snapshots/fingerprints.tests/TestCase.php, replace RefreshDatabase with the package's trait so every test inherits the behaviour.php artisan test twice. The first run pays the migration cost and writes the snapshot; the second should skip migrations entirely. That delta is your proof it's wired up.The first real use case is any suite where migrations, not assertions, dominate wall-clock time — typically a mature app with 100+ migration files where a full run spends minutes just building schema.
Trait swap in a base TestCase. Put the trait on your abstract TestCase rather than sprinkling it across test classes. Feature tests that already extend it get the speedup for free; unit tests that never touch the DB are unaffected.
Reason about the fingerprint, not the clock. v2.0.0 moves to content-based fingerprints. That means the snapshot is invalidated when migration file contents change, not when timestamps do. This is the behaviour you want: editing a migration or adding a new one triggers exactly one rebuild, and the next run is fast again. Treat "one slow run after a schema change" as the expected workflow, and don't try to work around it.
Multiple test databases. This release supports several test databases, which is the pattern you need for:
CI warming as an explicit step. In CI, run the suite once (or a dedicated warm-up command if the README exposes one) before the parallel matrix, and cache the snapshot directory keyed by the fingerprint. If the fingerprint is stable across jobs, every parallel shard starts from a restore instead of a migration run. Do not cache the snapshot without keying it on the fingerprint — a stale snapshot is exactly what the new migration-check is designed to catch, and you'll pay a rebuild (or worse, see confusing failures) when it trips.
Seeders and fixtures. If your suite depends on seeded reference data, keep it inside the snapshot rather than re-seeding per test. Check whether seeder paths participate in the fingerprint; if not, changing a seeder won't auto-invalidate and you'll need to force a rebuild.
Local inner loop. Day to day, you'll notice the package only when you touch migrations. Everything else — php artisan test --filter=..., single-file runs, --parallel — behaves like normal, just faster to start.
The new "databases still hold the migrations the fingerprint claims" check is your first debugging stop. If you see unexpected rebuilds or a test failure that looks like missing tables, the fingerprint and the snapshot DB have diverged — someone ran migrate:fresh, migrate:rollback, or a manual migration against the snapshot database, or a cached snapshot was restored under a fingerprint it doesn't match. Inspect the snapshot database directly before you go hunting in the fingerprint store.
Content-based means content. Reformatting a migration (whitespace, comments, import ordering) changes the hash and triggers a rebuild even though the schema is identical. If a linter or IDE reformats database/migrations on save, expect noisy rebuilds. Pin your formatter or exclude migrations from it.
Branch switching is the classic foot-gun. Checking out a branch with different migrations invalidates the snapshot, which is correct but means context-switching costs a full migration run. Don't try to keep per-branch snapshots locally; it isn't worth the bookkeeping.
Never point this at a development or production connection. It drops and restores databases. Use a dedicated test connection name and verify config/database.php for the testing env before the first run, especially in apps where .env.testing is incomplete and falls back to the default connection.
In-memory SQLite defeats the purpose. A :memory: connection dies with the process, so there's nothing to snapshot. Use a file-based SQLite database or MySQL/Postgres in tests if you want the benefit.
Verify the fallback, don't assume it. When a snapshot is missing (new connection, cleared cache, fresh clone), a well-behaved run should fall back to migrating. Confirm that's what happens rather than assuming — a silent misconfiguration that leaves migrations running every time looks like the package "not working" when it's actually never initialising.
Pin the version and track upstream. This is a community fork carrying the original's history; upstream may still ship fixes that the fork hasn't merged (and vice versa). Pin to 2.0.x, read the changelog on minor bumps, and if you hit a bug, check whether it exists upstream before reporting — the answer tells you where the fix will land. Laravel 13 support in a fork also means the package may move faster than the original, so don't assume feature parity in either direction.
Extension points worth knowing. Config for migration paths matters if you have package-provided or modular migrations outside database/migrations — anything not covered by the configured paths won't be in the fingerprint, and won't be applied to the snapshot. Audit that list once when you install, and again whenever you add a package that ships migrations.
How can I help you explore Laravel packages today?