skip to Main Content

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 {
        AppJobsProcessRunEndpoint::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 AppJobsProcessRunEndpoint::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?

2

Answers


  1. Chosen as BEST ANSWER

    I found a solution. Instead of using a facade for adding a job to the queue (AppJobsProcessRunEndpoint::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


  2. According to the docs, you should be able to test jobs through the mocks provided for the Queue.

    Queue::assertNothingPushed();
    // $apiRequest->storeRequestedData($requestedData);
    // Use assertPushedOn($queue, $job, $callback = null) to test your try catch
    Queue::assertPushedOn('run', AppJobsProcessRunEndpoint::class, function ($job) {
        // return true or false depending on $job to pass or fail your assertion
    });
    

    Making the line AppJobsProcessRunEndpoint::dispatch($apiRequest)->onQueue('run'); throw an exception is a bit complicated. dispatch() just returns an object and onQueue() is just a setter. No other logic is done there. Instead, we can make everything fail by messing with the configuration.

    Instead of Queue::fake();, override default queue driver with one that just won’t work: Queue::setDefaultDriver('this-driver-does-not-exist'); This will make every job dispatch fail and throw an ErrorException.

    Minimalist example:

    Route::get('/', function () {
        try {
            // Job does nothing, the handle method is just sleep(5);
            AJob::dispatch();
            return view('noError');
        } catch (Exception $e) {
            return view('jobError');
        }
    });
    
    namespace TestsFeature;
    
    use AppJobsAJob;
    use IlluminateFoundationTestingRefreshDatabase;
    use IlluminateFoundationTestingWithFaker;
    use IlluminateSupportFacadesQueue;
    use TestsTestCase;
    
    class AJobTest extends TestCase
    {
        /**
         * @test
         */
        public function AJobIsDispatched()
        {
            Queue::fake();
    
            $response = $this->get('/');
    
            Queue::assertPushed(AJob::class);
    
            $response->assertViewIs('noError');
        }
    
        /**
         * @test
         */
        public function AJobIsNotDispatched()
        {
            Queue::setDefaultDriver('this-driver-does-not-exist');
    
            $response = $this->get('/');
    
            $response->assertViewIs('jobError');
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search