connection()->getSchemaBuilder()->create('jobs', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('queue'); $table->longText('payload'); $table->tinyInteger('attempts')->unsigned(); $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); $table->index(['queue', 'reserved_at']); }); } protected function tearDown(): void { $this->app['db']->connection()->getSchemaBuilder()->drop('jobs'); parent::tearDown(); } public function testUniqueJobsAreNotDispatched() { Bus::fake(); UniqueTestJob::dispatch(); Bus::assertDispatched(UniqueTestJob::class); $this->assertFalse( $this->app->get(Cache::class)->lock($this->getLockKey(UniqueTestJob::class), 10)->get() ); Bus::fake(); UniqueTestJob::dispatch(); Bus::assertNotDispatched(UniqueTestJob::class); $this->assertFalse( $this->app->get(Cache::class)->lock($this->getLockKey(UniqueTestJob::class), 10)->get() ); } public function testLockIsReleasedForSuccessfulJobs() { UniqueTestJob::$handled = false; dispatch($job = new UniqueTestJob); $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); } public function testLockIsReleasedForFailedJobs() { UniqueTestFailJob::$handled = false; $this->expectException(Exception::class); try { dispatch($job = new UniqueTestFailJob); } finally { $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); } } public function testLockIsNotReleasedForJobRetries() { UniqueTestRetryJob::$handled = false; dispatch($job = new UniqueTestRetryJob); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); $this->artisan('queue:work', [ 'connection' => 'database', '--once' => true, ]); $this->assertTrue($job::$handled); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); UniqueTestRetryJob::$handled = false; $this->artisan('queue:work', [ 'connection' => 'database', '--once' => true, ]); $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); } public function testLockIsNotReleasedForJobReleases() { UniqueTestReleasedJob::$handled = false; dispatch($job = new UniqueTestReleasedJob); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); $this->artisan('queue:work', [ 'connection' => 'database', '--once' => true, ]); $this->assertTrue($job::$handled); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); UniqueTestReleasedJob::$handled = false; $this->artisan('queue:work', [ 'connection' => 'database', '--once' => true, ]); $this->assertFalse($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); } public function testLockCanBeReleasedBeforeProcessing() { UniqueUntilStartTestJob::$handled = false; dispatch($job = new UniqueUntilStartTestJob); $this->assertFalse($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); $this->artisan('queue:work', [ 'connection' => 'database', '--once' => true, ]); $this->assertTrue($job::$handled); $this->assertTrue($this->app->get(Cache::class)->lock($this->getLockKey($job), 10)->get()); } protected function getLockKey($job) { return 'laravel_unique_job:'.(is_string($job) ? $job : get_class($job)); } } class UniqueTestJob implements ShouldQueue, ShouldBeUnique { use InteractsWithQueue, Queueable, Dispatchable; public static $handled = false; public function handle() { static::$handled = true; } } class UniqueTestFailJob implements ShouldQueue, ShouldBeUnique { use InteractsWithQueue, Queueable, Dispatchable; public $tries = 1; public static $handled = false; public function handle() { static::$handled = true; throw new Exception; } } class UniqueTestReleasedJob extends UniqueTestFailJob { public $tries = 1; public $connection = 'database'; public function handle() { static::$handled = true; $this->release(); } } class UniqueTestRetryJob extends UniqueTestFailJob { public $tries = 2; public $connection = 'database'; } class UniqueUntilStartTestJob extends UniqueTestJob implements ShouldBeUniqueUntilProcessing { public $tries = 2; public $connection = 'database'; }