Implementing and Testing Laravel Job Queues

Implementing and Testing Laravel Job Queues

Laravel’s job queues allow you to defer time-consuming tasks, such as sending emails, processing uploads, or performing data imports, to background processes, improving the performance and responsiveness of your application. In this post, we’ll cover how to implement and test job queues in Laravel.

Step 1: Define the Problem (Implementing Job Queues)

Imagine you are working on a Laravel application that processes large files uploaded by users. Once a file is uploaded, you want to move the file processing task to the background to avoid blocking the user interface. To achieve this, you need to:

  • Create a job that processes the uploaded file.
  • Dispatch the job when the file is uploaded.
  • Write tests to ensure that the job is properly dispatched and executed.

Step 2: Set Up Queue Configuration

First, configure your queue settings. In the .env file, set up the queue driver, which can be database, redis, or any other supported driver:


QUEUE_CONNECTION=database

Next, make sure you have a queue table by running the migration:


php artisan queue:table
php artisan migrate

This will create a table in your database to store queued jobs.

Step 3: Create the Job

Now, create a new job class that will handle the file processing:


php artisan make:job ProcessUploadedFile

Edit the generated job class ProcessUploadedFile.php to include your file processing logic:


namespace App\Jobs;

use App\Models\File;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessUploadedFile implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $file;

    public function __construct(File $file)
    {
        $this->file = $file;
    }

    public function handle()
    {
        // File processing logic goes here
        $this->file->process();
    }
}

Step 4: Dispatch the Job

When a user uploads a file, dispatch the job in the controller or service that handles the file upload:


namespace App\Http\Controllers;

use App\Models\File;
use App\Jobs\ProcessUploadedFile;
use Illuminate\Http\Request;

class FileUploadController extends Controller
{
    public function store(Request $request)
    {
        // Store the uploaded file
        $file = File::create([
            'name' => $request->file('file')->getClientOriginalName(),
            'path' => $request->file('file')->store('uploads'),
        ]);

        // Dispatch the job to process the file in the background
        ProcessUploadedFile::dispatch($file);

        return response()->json(['message' => 'File uploaded successfully!']);
    }
}

Step 5: Writing Tests for Job Queues

Now, write tests to ensure that the job is dispatched correctly. Create a test class:


php artisan make:test FileUploadTest

Edit the test class to mock the job dispatch and verify that the job is queued when a file is uploaded:


namespace Tests\Feature;

use App\Models\File;
use App\Jobs\ProcessUploadedFile;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;

class FileUploadTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function it_dispatches_the_file_processing_job_after_upload()
    {
        // Mock the queue
        Queue::fake();

        // Simulate a file upload
        $response = $this->post('/upload', [
            'file' => UploadedFile::fake()->create('document.pdf', 1000),
        ]);

        // Assert the job was dispatched
        Queue::assertPushed(ProcessUploadedFile::class);
    }

    /** @test */
    public function the_job_processes_the_file_correctly()
    {
        // Create a file instance
        $file = File::factory()->create();

        // Dispatch the job
        ProcessUploadedFile::dispatch($file);

        // Run the job
        $this->artisan('queue:work --once');

        // Assert the file processing was completed
        $this->assertTrue($file->fresh()->is_processed);
    }
}

Step 6: Running the Queue Worker

To run the queue worker and process jobs in the background, use the following Artisan command:


php artisan queue:work

For testing environments, you can also use:


php artisan queue:work --once

This command processes one job and then stops, which is useful for running specific tests.

Step 7: Handling Failed Jobs

If a job fails, you need to handle it appropriately. You can define a failed method in your job class to take specific actions when the job fails:


public function failed(Exception $exception)
{
    // Notify the user about the failure, log the error, etc.
    Log::error('File processing failed', ['file' => $this->file, 'error' => $exception->getMessage()]);
}

Additionally, you can monitor failed jobs by creating a failed jobs table and re-queueing them:


php artisan queue:failed-table
php artisan migrate

Conclusion

In this post, you learned how to implement and test job queues in Laravel. By using job queues, you can efficiently handle background processes like file uploads, email sending, or API calls. Testing job dispatch and execution ensures that your background processes are reliable and your application performs optimally.