Writing blazing fast Laravel tests
No one wants slow tests. This is especially true if you have a large test suite with lots of database interactions. In this article, I'll introduce a package that helps make tests run much faster, with a bit of how it works under the hood; and then share some tips that altogether culminate in faster test run times, especially when using databases other than SQLite, such as MySQL, PostgreSQL, etc.
The Fundamental Issue with Database Testing
The way the default RefreshDatabase
trait works can sometimes be a bottleneck for your test suites. This is because this trait causes migrations to run every time you run your tests, which becomes exponentially slower as your migration files grow.
Although RefreshDatabase
only runs migrations at the beginning of tests, and then runs each test within a transaction, it is still not fast enough for large test suites, especially for codebases that have lots of migrations. This makes the initial run of the tests take a lot of time, which can be significant for sizeable codebases.
Swapping out RefreshDatabase
for FastRefreshDatabase
This is where the FastRefreshDatabase package comes in. It completely changes how we handle database testing by introducing a new refreshTestDatabase
method on top of the one provided by the RefreshDatabase
trait. It creates checksums of your migrations folder and Git branch.
Installation and Setup
composer require plannr/laravel-fast-refresh-database --dev
Replace the traditional trait in your base TestCase:
// tests/TestCase.php use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Plannr\Laravel\FastRefreshDatabase\Traits\FastRefreshDatabase; abstract class TestCase extends BaseTestCase { use CreatesApplication; use FastRefreshDatabase; }
For Pest PHP users:
// tests/Pest.php use Plannr\Laravel\FastRefreshDatabase\Traits\FastRefreshDatabase; uses(FastRefreshDatabase::class)->in('Feature');
Now, php artisan migrate:fresh
only runs when actual changes are detected, and not every single time you run your tests. This is a game-changer for large test suites.
Other Techniques for Speeding Up Tests
Load Squashed Migrations
For codebases with lots of migrations, consider squashing your migrations. This makes tests run faster because Laravel loads the migrations as a single SQL file blob using the native dump handler for your database such as mysqldump
for MySQL, which is much faster than running each migration file individually.
php artisan schema:dump
Usually, your local database is different from your testing database, so in practical terms, you want to dump for your testing database specifically,
php artisan schema:dump --database=testing
Now, whenever you run migrations as part of your testing workflow, the schema you dumped with the command above will be loaded first before running any migrations, saving time especially for large codebases where running each migration sequentially significantly slows down your test suites.
HTTP Response Faking for External APIs
When testing code that makes HTTP requests, avoid actual network calls by using Laravel's built-in HTTP fake capabilities:
Http::fake([ 'api.example.com/*' => Http::response(['status' => 'success'], 200) ]); $result = app(ExternalApiService::class)->fetchData(); expect($result)->toEqual(['status' => 'success']);
You may even take this a step further by preventing any HTTP requests from being made at all by using the Http::preventStrayRequests
method. You typically want to run this before running any of your tests to ensure no HTTP requests accidentally slip through. The setup may look like this in Pest:
uses(FastRefreshDatabase::class) ->beforeEach(function () { Http::preventStrayRequests(); }) ->in('Feature');
Now, all the time wasted on unnecessary HTTP requests is saved.
Use Test Doubles Strategically
Similar to the HTTP requests faking, you want to use the fake
method provided by Laravel where possible:
// Mock file system operations Storage::fake('public'); // Mock email sending Mail::fake(); // Mock job dispatching Queue::fake();
Key Takeaways
- Replace RefreshDatabase: Use
FastRefreshDatabase
to avoid running migrations on every test execution. - Squash migrations: Consolidate migration files into schema dumps for faster database setup.
- Mock external calls: Use
Http::fake()
andHttp::preventStrayRequests()
to eliminate network overhead. - Leverage Laravel's fakes: Mock file systems, emails, and queues with built-in testing utilities.
Conclusion
Test performance directly impacts how fast you move. The FastRefreshDatabase
package addresses the most common bottleneck in Laravel testing by intelligently managing database state, while Laravel's fakes eliminate unnecessary I/O operations.
These optimizations compound quickly in large codebases. What once took minutes can run in seconds, making test-driven development practical even for complex applications.