In this article, we'll explore two improved methods to make testing same class dispatched Laravel jobs a breeze. While the standard way gets the job done, it's like searching for a needle in a haystack when something goes wrong. We'll go through two approaches that shed light on exactly what's causing issues, making troubleshooting a whole lot easier.
Before we dive in, let's take a quick look at the code we're dealing with:
// web.php
<?php
Route::get('dispatch-jobs', \ App\Http\Controllers\JobController::class);
// JobController.php
<?php
namespace App\Http\Controllers;
use App\Jobs\TestJob;
class JobController extends Controller
{
public function __invoke()
{
TestJob::dispatch('John', 30, '[email protected]');
TestJob::dispatch('Will', 20, '[email protected]');
}
}
The standard way of testing Laravel jobs using the Queue::assertPushed
method is helpful, but it doesn't give you a clear picture of which job's parameters are causing trouble. It's like knowing something's wrong in your car but not where exactly. Good luck finding which job's parameter is invalid :).
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Jobs\TestJob;
use Illuminate\Support\Facades\Queue;
class JobControllerTest extends TestCase
{
/** @test */
public function assert_two_jobs_were_dispatched(): void
{
Queue::fake();
$response = $this->get('dispatch-jobs');
Queue::assertPushed(TestJob::class, 2);
// be aware this callable is called twice
// and passes if at least one job returns true
Queue::assertPushed(function (TestJob $job) {
return $job->name === 'John' &&
$job->age === 30 &&
$job->email === '[email protected]';
});
Queue::assertPushed(function (TestJob $job) {
return $job->name === 'Will' &&
$job->age === 20 &&
$job->email === '[email protected]';
});
}
}
Here's where it gets better. We can use the assertContains
method in a smarter way. This not only helps us know that something's off but also exactly which parameter is causing the issue. However there is still an issue of not knowing which job precisely caused the test to fail.
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Jobs\TestJob;
use Illuminate\Support\Facades\Queue;
class JobControllerTest extends TestCase
{
/** @test */
public function assert_two_jobs_were_dispatched(): void
{
Queue::fake();
$response = $this->get('dispatch-jobs');
Queue::assertPushed(TestJob::class, 2);
Queue::assertPushed(function (TestJob $job) {
$this->assertContains(
$job->name,
['John', 'Will'],
// it outputs the following message if assertion fails
"The name is {$job->name}, should be John or Will"
);
$this->assertContains(
$job->age,
[30, 20],
"The age is {$job->age}, should be 30 or 20"
);
$this->assertContains(
$job->email,
['[email protected]', '[email protected]'],
"The email is {$job->email}, should be [email protected] or [email protected]"
);
return true;
});
}
}
If you want an even more detailed view of what's going wrong, this approach got you covered. This time, we'll use an array of assertion parameter sets and an $index
passed to assertPushed
by reference. I know, I know, passed by reference euggh, that's awful, but do we have a choice here?
This approach not only lets you know when there's a problem but also highlights which job's parameters need your attention.
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Jobs\TestJob;
use Illuminate\Support\Facades\Queue;
class JobControllerTest extends TestCase
{
/** @test */
public function assert_two_jobs_were_dispatched(): void
{
Queue::fake();
$response = $this->get('dispatch-jobs');
Queue::assertPushed(TestJob::class, 2);
$index = 0;
$assertions = [
['name' => 'John', 'age' => 30, 'email' => '[email protected]'],
['name' => 'Will', 'age' => 20, 'email' => '[email protected]'],
];
Queue::assertPushed(function (TestJob $job) use (&$index, $assertions) {
$this->assertSame(
$assertions[$index]['name'],
$job->name,
"The name should be {$assertions[$index]['name']}"
);
$this->assertSame(
$assertions[$index]['age'],
$job->age,
"The age should be {$assertions[$index]['age']}"
);
$this->assertSame(
$assertions[$index]['email'],
$job->email,
"The age should be {$assertions[$index]['email']}"
);
$index++;
return true;
});
}
}
So, the takeaway is this: while these testing approaches might not be the fanciest and for everybody, they sure help when you're testing dispatched Laravel jobs. Understanding the trade-offs will help you choose the method that makes your testing life easier.