Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

troccoli's avatar

Mocking dispatched job

I have a queueable job that can throw an exception, something like

class JobA implements ShouldQueue
{
    use Queueable;

    public function handle(): void
    {
        try {
            // Do something
        } catch (Exception $e) {
            // Process exception
            throw $e;
        }
    }
}

I then have a command that dispatches the job synchronously, something like

class MyCommand extends Command
{
    protected $signature = 'app:my-command';

    public function handle(): int
    {
        try {
            JobA::dispatchSync();
        } catch (Exception $e) {
            $this->error($e->getMessage());
            return self::FAILURE;
        }

        return self::SUCCESS
    }
}

Now, I'm trying to write a test for the command, so my instinct was to mock the job to throw an exception and then check that the message is displayed

it('shows the exception message', function () {
    $this->instance(
        JobA::class,
        Mockery::mock(JobA::class, function (MockInterface $mock) {
            $mock->shouldReceive('handle')->once()->andThrow(Exception::class, 'Something went wrong');
        })
    );

    $this->artisan('app:my-command')
        ->assertFailed()
        ->expectsOutput('Something went wrong');
});

The test fails with

Output "Something went wrong" was not printed.

It looks like JobA::dispatchSync() does not use the mocked Job.

Does anybody know how I could write such a test?

0 likes
2 replies
LaryAI's avatar
LaryAI
AI
Best Answer
Level 58

To test the behavior of your command when a job throws an exception, you need to ensure that the job is properly mocked and that the mock is used when the job is dispatched. The issue you're facing is likely because dispatchSync() is not using the mocked instance. Instead, you can use Laravel's Bus facade to fake job dispatching and control the behavior of the job.

Here's how you can modify your test to achieve this:

  1. Use the Bus facade to fake job dispatching.
  2. Define the behavior of the job when it is dispatched.

Here's the updated test:

use Illuminate\Support\Facades\Bus;
use App\Jobs\JobA;
use Exception;

it('shows the exception message', function () {
    // Fake the bus to intercept job dispatching
    Bus::fake();

    // Define the behavior of the job when it is dispatched
    Bus::shouldReceive('dispatchSync')
        ->with(Mockery::type(JobA::class))
        ->andThrow(new Exception('Something went wrong'));

    // Run the artisan command
    $this->artisan('app:my-command')
        ->assertFailed()
        ->expectsOutput('Something went wrong');
});

Explanation:

  • Bus::fake(): This fakes the job dispatching, allowing you to intercept and control the behavior of jobs.
  • Bus::shouldReceive('dispatchSync'): This sets up an expectation that when dispatchSync is called with an instance of JobA, it should throw an exception.
  • Mockery::type(JobA::class): This ensures that the expectation is set for any instance of JobA.

By using the Bus facade, you can effectively test how your command handles exceptions thrown by jobs without actually executing the job logic.

martinbean's avatar

@troccoli Why are just catching any exception and then black-holding it? Yes, you’re manually returning a failure response, but the Artisan command does that anyway if an exception happens. It’s just now, any exception that does happen, is not going to be logged, so you’re never going to know if an error happens with this command and if you, well you’re never going to see the error message and stack trace because it doesn’t get logged.

Remove the try/catch block. It’s useless:

public function handle(): int
{
    JobA::dispatchSync();

    // If an exception happens, it will be logged
    // and command will return failure exit code

    return self::SUCCESS;
}

As for the test, mock the job and throw an exception. You’ll then be able to test if your command exits appropriately:

$this->mock(JobA::class, function (MockInterface $mock) {
    $mock->shouldReceive('dispatchSync')->once()->andThrow(new Exception('Some exception.'));
});

$this
    ->artisan('app:my-command')
    ->assertFailed();

But this test is pretty pointless. You’re testing if a command returns a failed exit code if an exception is encountered? Cool, that’s what Laravel does any way, so you’re just testing the framework’s behaviour at this point.

You should be instead testing your job class, and the paths where it can and cannot throw an error.

Please or to participate in this conversation.