How to Migrate and seed before the full test suite in Laravel with in memory database?

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

How to Migrate and Seed Before the Full Test Suite in Laravel with an In-Memory Database

Setting up a robust testing environment is crucial for maintaining stable Laravel applications. When dealing with database migrations and seeding, developers often run into performance bottlenecks. If you execute Artisan::call('migrate') and Artisan::call('db:seed') inside every single test method's setup hook (like $this->setUp()), you are needlessly recreating and populating the database thousands of times, drastically slowing down your test suite.

This post dives into the correct, efficient way to handle large-scale database initialization for Laravel tests, specifically when utilizing in-memory SQLite databases, ensuring your testing remains fast and reliable.

The Pitfall of Redundant Setup

The common approach of placing setup commands within $this->setUp() is intuitive but inefficient for large projects. While this guarantees that each test runs against a fresh state (which is good), it forces the entire migration process and data seeding to execute repeatedly.

You correctly noted the issue with setUpBeforeClass(). Often, methods like createApplication() or other framework bootstrapping steps reset the context in ways that prevent class-level setup from persisting across all test methods, especially when dealing with dynamic database connections. The core challenge is separating the application bootstrap from the database state initialization.

The Efficient Solution: Class-Level Initialization

The solution lies in leveraging PHPUnit's lifecycle hooks to perform heavy, one-time operations that only need to happen once per test class execution, rather than once per test method. We achieve this by moving the migrations and seeding outside of the standard setup loop.

For large setups, we should focus on running these commands before any specific test methods are invoked, ensuring the database structure is ready before testing data interactions.

Step 1: Setting up the In-Memory Connection

Since you are using an in-memory SQLite database for speed, ensure your test setup correctly configures this environment first. This often involves setting up the connection parameters within the base test class.

php
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

class DatabaseTest extends BaseTestCase
{
    use RefreshDatabase; // Provides transactional rollback for simpler tests

    protected function setUp(): void
    {
        parent::setUp();
        // Standard setup runs here, but we will move heavy work to the class level.
    }

    /**
     * Run database migrations and seeders once before any test methods execute.
     */
    public static function setUpBeforeClass(): void
    {
        // Ensure the application is booted enough to run Artisan commands
        $app = require __DIR__ . '/../bootstrap/app.php';

        // 1. Run Migrations
        $this->artisan('migrate');

        // 2. Run Seeders (using JSON fixtures as you mentioned)
        // This ensures the database is populated with initial data for all tests.
        $this->artisan('db:seed', ['--class' => 'DatabaseSeeder']);
    }

    // ... rest of your tests
}

Step 2: Why setUpBeforeClass Works Here

By using the static method setUpBeforeClass(), you execute the migration and seeding logic only once when the test class is loaded. This effectively pre-populates the in-memory SQLite database before any individual test methods (which rely on the framework booting) begin execution.

This approach delegates the heavy lifting to the class level, keeping your per-test setup clean, fast, and focused only on setting up specific test variables rather than rebuilding the entire schema repeatedly. This aligns with best practices for optimizing test performance, especially when working with complex Eloquent relationships, as discussed in modern Laravel development patterns.

Conclusion

Migrating and seeding before the full test suite is not about finding a single magic command; it’s about correctly utilizing PHPUnit's lifecycle hooks to separate application bootstrapping from data initialization. By moving migrate and db:seed into the static setUpBeforeClass() method, you achieve significant performance gains without sacrificing the isolation required for robust testing. This strategy allows you to maintain a fast, reliable test suite while adhering to strong architectural principles within your Laravel application.