Search code examples
laravelunit-testingmockingphpunitjobs

How to mock a Job object in Laravel?


When it comes to Queue testing in Laravel, I use the provided Queue Fake functionality. However, there is a case where I need to create a Mock for a Job class.

I have the following code that pushes a job to a Redis powered queue:

  $apiRequest->storeRequestedData($requestedData); // a db model
  // try-catch block in case the Redis server is down
    try {
        App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');
        $apiRequest->markJobQueued();
    } catch (\Exception $e) {
        //handle the case when the job is not pushed to the queue
    }

I need to be able to test the code in the catch block. Because of that, I'm trying to mock the Job object in order to be able to create a faker that will throw an exception.

I tried this in my Unit test:

   ProcessRunEndpoint::shouldReceive('dispatch');

That code returns: Error: Call to undefined method App\Jobs\ProcessRunEndpoint::shouldReceive(). I also tried to swap the job instance with a mock object using $this->instance() but it didn't work as well.

That said, how can I test the code in the catch block?


Solution

  • I found a solution. Instead of using a facade for adding a job to the queue (App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');), I injected it into the action of the controller:

        public function store(ProcessRunEndpoint $processRunEndpoint)
        {
            // try-catch block in case the Redis server is down
            try {
                $processRunEndpoint::dispatch($apiRequest)->onQueue('run');        
            } catch (\Exception $e) {
                //handle the case when the job is not pushed to the queue
            }
        }
    

    With this, the job object is resolved from the container, so it can be mocked:

            $this->mock(ProcessRunEndpoint::class, function ($mock) {
                $mock->shouldReceive('dispatch')
                    ->once()
                    ->andThrow(new \Exception());
            });
    

    Although still not sure why the shouldReceive approach doesn't work for the facade: https://laravel.com/docs/8.x/mocking#mocking-facades