如何在 Laravel 中模拟 Job 对象?

How to mock a Job object in Laravel?

当谈到 Laravel 中的队列测试时,我使用提供的 Queue Fake 功能。但是,在某些情况下,我需要为 Job class.

创建一个 Mock

我有以下代码将作业推送到 Redis 支持的队列:

  $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
    }

我需要能够测试 catch 块中的代码。因此,我试图模拟 Job 对象,以便能够创建一个会抛出异常的伪造者。

我在单元测试中试过这个:

   ProcessRunEndpoint::shouldReceive('dispatch');

那个代码returns:Error: Call to undefined method App\Jobs\ProcessRunEndpoint::shouldReceive()。 我还尝试使用 $this->instance() 将作业实例与模拟对象交换,但效果不佳。

也就是说,我如何测试 catch 块中的代码?

根据 docs,您应该能够通过为队列提供的模拟来测试作业。

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

App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');行抛出异常有点复杂。 dispatch() 只是 returns 一个对象,onQueue() 只是一个 setter。那里没有其他逻辑。相反,我们可以通过搞乱配置来让一切都失败。

而不是Queue::fake();,用一个无法工作的默认队列驱动程序覆盖:Queue::setDefaultDriver('this-driver-does-not-exist');这将使每个作业调度失败并抛出一个ErrorException

极简示例:

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 Tests\Feature;

use App\Jobs\AJob;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;

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');
    }
}

我找到了解决办法。我没有使用 facade 将作业添加到队列 (App\Jobs\ProcessRunEndpoint::dispatch($apiRequest)->onQueue('run');),而是将其注入到控制器的操作中:

    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
        }
    }

有了这个,作业对象从容器中解析出来,所以它可以被模拟:

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

尽管仍然不确定为什么 shouldReceive 方法对外观不起作用:https://laravel.com/docs/8.x/mocking#mocking-facades