Summer Sale! All accounts are 50% off this week.

wesleya's avatar

Testing job dispatch in Laravel

Is there a way to test that a job is dispatched with the correct parameters in Laravel phpunit? For example, I have a job that searches for images that should be deleted. For each image it finds, it will dispatch another job to delete the image (and some other cleanup).

I want to test that it's only dispatching jobs for images that should actually be deleted, and not deleting anything it's not supposed to. Here's a snippet:

foreach($imagesToDelete as $image)
{
    $job = (new DeleteTheImage($image);
    $this->dispatch($job);
}

For tests right now I'm just using $this->expectsJobs(App\Jobs\DeleteTheImage::class). But it's not really checking anything useful. I want to check that the right $image was passed through. On a related note, is there a way to test expected number of jobs dispatched? Thanks for the help!

0 likes
12 replies
REBELinBLUE's avatar

Did you find a solution to this, I want to do both things as well (check the right thing is passed and the correct number)

Also, how can you make the mock job throw an exception so the parent job can handle it

1 like
icalderon's avatar

I like to test my jobs in tinker in the following way:


$job = (new \App\Jobs\MyJob($data));

dispatch($job);

It work's great for me, please let me know if it was helpful for you.

1 like
ryan.dial's avatar

This question might be a year old, but I had it today. Here's the solution I came up with.

Using Queue:fake() exposes special assertions for Queues. assertPushed was particularly helpful for me: https://github.com/laravel/framework/blob/5.5/src/Illuminate/Support/Testing/Fakes/QueueFake.php#L25

<?php
namespace Tests\Unit;

use App\Jobs\SendSMS;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;

class SendChaseNotificationTest extends TestCase
{
    public function testSendSmsDispatched()
    {
        Queue::fake();

        // Logic that should dispatch the SendSMS job

        Queue::assertPushed(SendSMS::class, function ($job) {
            return strlen($job->message) < 140;
        });
    }
}
14 likes
OneSheep's avatar

If your queue connection is set to "sync" in your testing environment, then you can use

$this->expectsJobs(DeleteTheImage::class);
1 like
florenxe's avatar

Hello, I'm using Laravel6x, I have a test to check that the UploadImageJob is dispatched and I used the Queue::fake() and the assertPushed to check if the job was dispatched. It keeps failing even though it shows from the logs that the job was called and does the image upload. Let me share some code for better understanding

PhotoUploadService.php

public function upload(Model $resource, Request $request)
	{
		if ($request->file('photo')) {
			PhotoUploadJob::dispatchNow($resource, $request);
		}
	}

PhotoUploadServiceTest.php

public function it_dispatches_the_photo_upload_job_if_the_request_contains_a_file()
	{
		Queue::fake();

		$this->actingAs($this->user);

		$comment = factory(Comment::class)->create([
			'author_id' => $this->user->first()->getKey(),
			'content' => 'lorem ipsum'
		]);

		$service = new PhotoUploadService();

		$request = new Request();
		$request->files->set('photo', UploadedFile::fake()->image('avatar.jpeg'));

		$service->upload($comment->first(), $request);

		Queue::assertPushed(PhotoUploadJob::class, function ($job) {
			return $job->resource instanceof Model;
		});
	}
PhotoUploadJob.php

public function handle()
    {
		Log::info('Uploading image ...');

		try {
			$ext = $this->photo->getClientOriginalExtension();

			if (in_array($ext, $this->supported_extensions)) {
				Storage::disk('public')->put($this->photo->getFilename() . '.' . $ext, File::get($this->photo));

				Log::info('Image upload completed.');

				$photo_path = $this->request->get('resource_photo_path');

				if (isset($photo_path)) {
					$this->resource->update([$photo_path => $this->photo->getFilename() . '.' . $ext]);
				}
			} else {
				throw new UnsupportedMediaTypeHttpException('Image Extension not supported');
			}
		} catch (UnsupportedMediaTypeHttpException $e){
			Log::info('Upload failed', ['message' => $e->getMessage()]);
		}
    }

As you can see, the PhotoUploadService makes a call to dispatch the PhotoUploadJob immediately using the dispatchNow method. Even though the test fails, I can see the job outputs in the logs. What am I missing here please?

gsmofgsm's avatar

Maybe your PhotoUploadJob is not queueable, so Queue::assertPushed would fail. And what is that Model in your test? If the callback function returns false, the assertion would also fail. Hoop this would help.

da_Mask's avatar

Hey, what you missed I think is that by using dispatchNow() you aren't queueing the job, but sending it syncronously. Therefore Queue::fake() can't intercept it.

lapubell's avatar

I have a similar but slightly different issue. I have a model that is created during the job, and an observer watching that model. When a new model is created a different job is dispatched, and I want to verify that the correct jobs are called.

I'm using the fake Bus as documented here https://laravel.com/docs/8.x/mocking#bus-fake

The problem is that the second job is never called, since the first one isn't actually called.

Anyone have any ideas on how to verify the second job is called correctly? I think for now I'll just test to make sure side effects are invoked (mailables, db entries, etc).

james.ritchie's avatar

If you want to let the first job run as normal and assert that the second job is dispatched, then you can pass in an array to Bus::fake() listing the class names of the jobs that the fake should listen for. Any jobs not included will run as normal.

Please or to participate in this conversation.