Running Laravel Tests in Parallel: A Guide for Faster CI Pipelines

By Amas
Running Laravel Tests in Parallel: A Guide for Faster CI Pipelines

If you're reading this article, kudos to you! 🎉

Writing tests is essential for building reliable software, and the fact that you're already doing it deserves recognition. Keep up the great work! 👏

As your Laravel application grows, so does your test suite. Running tests sequentially can become time-consuming, slowing down your development workflow. Fortunately, Laravel allows running tests in parallel, significantly reducing execution time. In this article, we'll explore how to run Laravel tests in parallel and what considerations to keep in mind.

Enabling Parallel Testing in Laravel

By default, Laravel and Pest/PHPUnit execute tests sequentially within a single process. However, you can speed up test execution by running them simultaneously across multiple processes. To enable parallel testing, follow these steps:

1. Install the Parallel Testing Package:

composer require brianium/paratest --dev

2. Run Tests in Parallel:

php artisan test --parallel

By default, Laravel will create as many processes as there are available CPU cores. You can specify the number of processes manually:

php artisan test --parallel --processes=4

3. Recreating Test Databases:

Laravel automatically handles database creation and migration for each parallel process, appending a unique process token to the database name (e.g., your_db_test_1, your_db_test_2). To recreate test databases before running tests, use:

php artisan test --parallel --recreate-databases


Considerations When Running Tests in Parallel

Parallel testing introduces new challenges, so your test suite must be structured correctly to avoid those problems. Here are key considerations:

1. Database Isolation

Each test process must use an isolated database instance to prevent conflicts.

  • Separate Databases Per Process: Laravel automatically creates separate test databases per process.

  • Use SQLite In-Memory Databases: This is the simplest way to ensure isolation.

  • Truncate Instead of Migrate: Using RefreshDatabase or DatabaseTruncation strategies speeds up tests.

2. Handling Parallel Testing Hooks

You may need to set up or tear down specific resources for parallel execution. Laravel provides the ParallelTesting facade to handle this:

namespace App\Providers;

use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\ParallelTesting;
use Illuminate\Support\ServiceProvider;
use PHPUnit\Framework\TestCase;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        ParallelTesting::setUpProcess(function (int $token) {
            // Setup logic for each process
        });

        ParallelTesting::setUpTestCase(function (int $token, TestCase $testCase) {
            // Setup logic for each test case
        });

        ParallelTesting::setUpTestDatabase(function (string $database, int $token) {
            Artisan::call('db:seed');
        });

        ParallelTesting::tearDownTestCase(function (int $token, TestCase $testCase) {
            // Cleanup logic per test case
        });

        ParallelTesting::tearDownProcess(function (int $token) {
            // Cleanup logic per process
        });
    }
}


3. Accessing the Parallel Testing Token

Each test process has a unique process "token" that helps segment resources. You can retrieve it using:

$token = ParallelTesting::token();

This token is a unique string identifier assigned to each test process, helping to isolate resources across parallel executions. For instance, Laravel automatically appends this token to the names of test databases created for each parallel test process. Additionally, if your application has custom shared resources, such as caches, files, or session storage, you can use this token to further segment and isolate them, preventing conflicts between parallel test runs.


4. File System Conflicts

If your tests involve file operations, conflicts can arise due to concurrent writes.

  • Use Storage::fake() to isolate disk operations.

    class FileUploadTest extends TestCase
    {
        public function test_file_upload()
        {
            // Fake the storage disk
            Storage::fake('public');
    
            // Create a fake file
            $file = UploadedFile::fake()->image('test-image.jpg');
    
            // Perform a file upload request
            $response = $this->post('/upload', [
                'file' => $file,
            ]);
    
            // Assert the file was stored
            Storage::disk('public')->assertExists('uploads/test-image.jpg');
    
            // Assert the response was successful
            $response->assertStatus(200);
        }
    }
  • If using storage/app, ensure each process has a unique directory.


5. Race Conditions in Jobs & Events

Parallel execution may cause unexpected behavior in queued jobs and events. Some things that could make your life easier:

  • Disable queues during tests: QUEUE_CONNECTION=sync

  • Ensure event-driven behavior does not depend on execution order.

  • Use Event::fake() to assert that events were fired without executing their listeners.


6. Caching and Session Issues

Parallel tests sharing the same cache or session store can interfere with each other.

Use an in-memory cache:

CACHE_DRIVER=array

Use an in-memory session driver:

SESSION_DRIVER=array


Improving Test Execution Time Further

In addition to parallel testing, you can speed up Laravel tests by:

  • Optimizing Database Indexes: Reducing query execution times if your tests invove handling much data.

  • Running Only Necessary Tests: Use php artisan test --filter=MyTest to execute a subset of tests separately.

Conclusion

Running Laravel tests in parallel can significantly improve performance, reducing execution time and making your CI/CD pipeline more efficient, and also improving your feedback loop locally while developing new features.

Just be mindful of any shared resources that are being used across tests, and you should be good to go. 

Happy testing! 🤘

 

Share this post.
Liked that? Subscribe to our newsletter for more
Ship fast & don't reinvent the wheel

Build your SaaS using SaaSykit

SaaSykit is a SaaS boilerplate that comes packed with all components required to run a modern SaaS software.

Don't miss this

You might also like