imTigger / laravel-job-status

Add ability to track Job progress, status and result dispatched to Queue.
MIT License
407 stars 60 forks source link

TypeError: Argument 1 passed to Imtigger\LaravelJobStatus\LaravelJobStatusServiceProvider::updateJobStatus() must be an instance of Illuminate\Contracts\Queue\Job #4

Closed dsge closed 7 years ago

dsge commented 7 years ago

I am trying to write a test for my Laravel Job which uses this library. The thought process is this:

I am running into a little incompatibility after firing the JobProcessed event.

Here is my code and the exception:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\AudioMedia;
use Imtigger\LaravelJobStatus\Trackable;

class ConvertAudio implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Trackable;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(AudioMedia $model, array $options)
    {
        $this->prepareStatus();
        /* ... */
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        /* ... */
    }
<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Jobs\ConvertAudio;
use App\AudioMedia;
use Illuminate\Queue\Events\JobProcessed;

class ConvertAudioJobTest extends TestCase
{
    //use DatabaseTransactions;
    /* --- */

    public function testJobStatusShouldBeCompleted()
    {
        $model = factory(AudioMedia::class)->create();
        $ffmpegMock = /*...*/

        $job = new ConvertAudio($model, [
            ['format' => new \FFMpeg\Format\Audio\Mp3()]
        ]);
        $job->ffmpeg = $ffmpegMock;
        $job->handle();

        event(new JobProcessed("foo", $job));
    }
}
TypeError: Argument 1 passed to Imtigger\LaravelJobStatus\LaravelJobStatusServiceProvider::updateJobStatus() must be an instance of Illuminate\Contracts\Queue\Job, instance of App\Jobs\ConvertAudio given, called in /usr/share/nginx/html/vendor/imtigger/laravel-job-status/src/LaravelJobStatusServiceProvider.php on line 35

If I try to implement Illuminate\Contracts\Queue\Job (by extending Illuminate\Queue\Jobs\Job) I get this:

PHP Fatal error:  Illuminate\Queue\Jobs\Job and Illuminate\Bus\Queueable define the same property ($queue) in the composition of App\Jobs\ConvertAudio. However, the definition differs and is considered incompatible. Class was composed in /usr/share/nginx/html/app/Jobs/ConvertAudio.php on line 21

Am I missing something?

Edit: I am using

imTigger commented 7 years ago

I haven't tried to emit the JobProcessed event manually.

But the incompatibility seems due to how Laravel handles "Job" internally. When a "Job" is dispatched to queue, it wait for worker to pick it up. The worker then pick up the "Job" as Illuminate\Queue\Jobs\[SyncJob|RedisJob|BeanstalkdJob...] that implements Illuminate\Contracts\Queue\Job, and that Job actually contains the "Job" you defined in user-space. Although confusing, they are totally different things

Furthermore, the JobProcessing/JobProcessed/JobFailed/JobExceptionOccurred events emitted by Laravel, actually contains the [SyncJob/RedisJob/BeanstalkdJob...] (that again contains your "Job"), not the "Job" you defined directly But I am not sure if we can initiate instances of those Job directly

dsge commented 7 years ago

Thank you for your response.

Turns out you were right, those are two separate things. I have also overcomplicated the problem. Since laravel's default phpunit.xml sets the QUEUE_DRIVER to sync in tests I should have simply just dispatched my ConvertAudio job and let Laravel do it's thing.

This is what I did initally:

$job = new ConvertAudio($model, [
    ['format' => new \FFMpeg\Format\Audio\Mp3()]
]);
$job->ffmpeg = $ffmpegMock;

$status = \Imtigger\LaravelJobStatus\JobStatus::find($model->fresh()->last_job_status_id);
$this->assertEquals("queued", $status->status);

$job->handle();
event(new JobProcessed("foo", $job));

$this->assertEquals("finished", $status->fresh()->status);

But what I should have done is:

$job = new ConvertAudio($model, [
    ['format' => new \FFMpeg\Format\Audio\Mp3()]
]);
$job->ffmpeg = $ffmpegMock;

$status = \Imtigger\LaravelJobStatus\JobStatus::find($model->fresh()->last_job_status_id);
$this->assertEquals("queued", $status->status);

dispatch($job);

$this->assertEquals("finished", $status->fresh()->status);

I should have never tried to call ->handle() on my job manually. Now this works as expected and it passes the test.

I am closing this issue since this is not related to this library.

imTigger commented 7 years ago

Anyway, thank you for sharing your findings :)