首页  >  文章  >  后端开发  >  Tips for testing queued jobs in Laravel

Tips for testing queued jobs in Laravel

Mary-Kate Olsen
Mary-Kate Olsen原创
2024-09-19 22:20:33432浏览

When working with Laravel applications, it’s common to encounter scenarios where a command needs to perform an expensive task. To avoid blocking the main process, you might decide to offload the task to a job that can be processed by a queue.

Let’s walk through an example. Imagine the command app:import-users needs to read a large CSV file and create a user for each entry. Here’s what the command might look like:

/* ImportUsersCommand.php */

namespace App\Console\Commands;

/*...*/

class ImportUsersCommand extends Command
{
    protected $signature = 'app:import-users';

    public function handle()
    {
        dispatch(new ImportUsersJob());

        $this->line('Users imported successfully.');
        $this->line('There are: ' . User::count(). ' Users.');
    }
}

In this example, the command dispatches a job to handle the reading of the file and the creation of users. Here’s how the ImportUsersJob.php might look:

/* ImportUsersJob.php */

namespace App\Jobs;

/*...*/

class ImportUsersJob implements ShouldQueue
{
    public function handle(FileReader $reader): void
    {   
        foreach($reader->read('users.csv') as $data) {
            User::create([
                'name' => $data['name'], 
                'email' => $data['email'],
            ]);
        }
    }
}

When testing this feature, a typical test for the command might look like this:

/* ImportUsersCommandTest.php */

namespace Tests\Feature;

/*...*/

class ImportUsersCommandTest extends TestCase
{
    use RefreshDatabase;

    public function test_it_processes_the_file(): void
    {
        Storage::fake('local')->put('users.csv', "...");

        $this->artisan('app:import-users')
            ->expectsOutput('Users imported successfully.')
            ->expectsOutput('There are: 10 Users.')
            ->assertSuccessful();

        $this->assertDatabaseCount('users', 10);
    }
}

At first glance, this test seems to work perfectly. Running the test suite shows a successful result:

Tips for testing queued jobs in Laravel

Real World Execution

However, when you run the app:import-users command in a real environment, you might get an unexpected result:

Tips for testing queued jobs in Laravel

As you can see, the command output indicates that there are 0 users in the database. So, Why does this happen?

The reason is that the job is dispatched to a queue, so it doesn’t run synchronously with the command execution. The users will be created only when the queue processes the job later.

Why does the test pass?

The test suite uses the sync queue driver by default, meaning jobs are processed synchronously during the test. As a result, the job runs immediately, giving the idea that everything works as expected.

While this behavior is acceptable in the test environment, it’s important to recognize that real-world results depend on the QUEUE_CONNECTION configuration in your production environment. And given your project requirements, you might know that the job will be processed in an async queue.

Once you’re aware of this distinction, you may want to improve your tests to avoid “false positives”.

Testing your job is dispatched

First, it’s important to verify that the command actually dispatches the job, regardless of whether the job is processed synchronously or asynchronously. Here’s how to test that:

/* ImportUsersCommandTest.php */

namespace Tests\Feature;

/*...*/

class ImportUsersCommandTest extends TestCase
{    
    public function test_it_dispatches_the_job(): void
    {
        Queue:fake();

        $this->artisan('app:import-users')
            ->expectsOutput('Process has been queued.')
            ->assertSuccessful();

        Queue::assertPushed(ImportUsersJob::class);
    }
}

Testing your job is processed

Once you’ve confirmed that the job is dispatched, you can test the actual work performed by the job in a separate test. Here’s how you might structure the test for the job:

/* ImportUsersJobTest.php */

namespace Tests\Feature;

/*...*/

class ImportUsersJobTest extends TestCase
{
    use refreshDatabase;

    public function test_it_processes_the_file()
    {
        Storage::fake('local')->put('users.csv', "...");

        app()->call([new ImportUsersJob(), 'handle']);

        $this->assertDatabaseCount('users', 10);
    }
}

This ensures that the job performs the necessary work, regardless of whether it’s processed by a queue or synchronously.

Handling edge cases

As in real life, edge cases might happen and you should be ready for these.

Laravel’s queue system, according to your workers configuration, will retry jobs when an exception occurs, and if retries are exceeded, the job will be marked as failed.

So, what happens if the file doesn’t exist? You need to handle such edge cases by validating inputs and throwing exceptions when necessary.

Here’s how you might handle this in your job:

/* ImportUsersJobTest.php */

namespace App\Jobs;

/*...*/

class ImportUsersJob implements ShouldQueue
{
    use Queueable;

    public function handle(FileReader $reader): void
    {   
        if(!Storage::disk('local')->exists('users.csv')){
            throw new Exception('The users.csv file doesn\'t exist.')
        }

        foreach($reader->read('users.csv') as $data) {
            User::create([
                'name' => $data['name'], 
                'email' => $data['email'],
            ]);
        }
    }
}

Here’s how you’d test this scenario:

/* ImportUsersJobTest.php */

namespace Tests\Feature;

/*...*/

class ImportUsersJobTest extends TestCase
{
    use refreshDatabase;

    /*...*/

    public function test_it_fails_when_file_doesnt_exist(): void
    {
        Storage::fake('local');

        $this->expectException(Exception::class);
        $this->expectExceptionMessage('The users.csv file doesn\'t exist.');

        dispatch(new ImportUsersJob());
    }
}

Final thoughts

This approach ensures that your tests more accurately reflect how jobs will be processed in the real world.
The same strategy can be applied when a controller dispatches a job to a queue or where a event listener is queued.
As always, adjust these practices to fit your project and team.

I’d love to hear your thoughts!

以上是Tips for testing queued jobs in Laravel的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn