therezor / laravel-transactional-jobs

Run Laravel job inside transaction (after transaction is committed, or cancel if rolled back)
MIT License
45 stars 8 forks source link

How to verify in unit tests #11

Closed wesnick closed 3 years ago

wesnick commented 3 years ago

Hi,

In a normal test you might mock the Queue

Queue::fake();

Queue::assertPushedOn('queue-name', ShipOrder::class);

How can I do this when this library is enabled? Outside of reflection and I don't see how I can do this

        $d = app(Dispatcher::class);
        if ($d instanceof TransactionalDispatcher) {
            // use reflection to fetch pendingCommands
        }
therezor commented 3 years ago

What kind of mocking do you need? Can you please provide more detailed example?

BTW: This on will fail, as job is not pushed yet

        Queue::fake();
        DB::transaction(function () {
            TestJob::dispatch();
            Queue::assertPushedOn('lol', TestJob::class);
        });

This one will be successful

        Queue::fake();
        DB::transaction(function () {
            TestJob::dispatch();

        });

         Queue::assertPushedOn('lol', TestJob::class);
wesnick commented 3 years ago

Hi, sorry for not getting back sooner.

Here is an example of how I am verifying a Job was pushed to the Queue when this library is enabled: NOTE: the following is pseudo-code.


    public function testPost()
    {
        $myPayload = ['foo' => 'bar'];
        // POST to an endpoint that has a model listener that pushes a job, 
        // this controller must create several database objects and so is nested in a transaction
        $response = $this->postJson('/my-endpoint', $myPayload);
        $myModelId = $response->json('data.id');

        $d = app(Dispatcher::class);
        if ($d instanceof TransactionalDispatcher) {
            $refl = new \ReflectionClass($d);
            $reflProp = $refl->getProperty('pendingCommands');
            $reflProp->setAccessible(true);
            $commands = $reflProp->getValue($d);

            $jobs = array_pop($commands);
            $ids = Arr::pluck($jobs, 'my_model.id');

            $this->assertTrue(in_array($myModelId, $ids, true));
        }
    }

I would like to be able to follow the existing pattern in Laravel for mocking the Queue.

public function setUp()
{
    // something like this...
    Dispatcher::fake();
}

then in my Test


    public function testPost()
    {
        $myPayload = ['foo' => 'bar'];
        // POST to an endpoint that has a model listener that pushes a job, 
        // this controller must create several database objects and so is nested in a transaction
        $response = $this->postJson('/my-endpoint', $myPayload);
        $myModelId = $response->json('data.id');

        $d = app(Dispatcher::class);
        $d->assertPushed(MyJob::class, function (MyJob $job) use ($myModelId) {
               return $job->my_model->id === $myModelId;
        });
wesnick commented 3 years ago

I can collaborate on or author a PR depending on how you want to this to work.

therezor commented 3 years ago

After you run post request, job should be in queue $d->assertPushed() should work fine; Please update to latest version and try that approach again.

wesnick commented 3 years ago

Ah, right, I see now my test uses the DatabaseTransactions trait, so the job never gets enqueued because transaction level starts at 1 and never reaches 0. This is the underlying issue.

therezor commented 3 years ago

Great. So I am closing this issue for now. If you need some functionality to test pending jobs stack, let me know. Also feel free to make PRs with improvements.... Have a nice day.

wesnick commented 3 years ago

This is the pattern I am using now in tests when this AfterTransactions is enabled on Jobs

    public function setUp(): void
    {
         parent::setUp();
         Queue::fake;
    }

    public function testSomething()
    {
          // ... some code that generates a Job...
        Queue::hasPushed(MyJob::class); // false
        $d = app(Dispatcher::class);
        $d->commitTransaction(); // Does not actually commit, just decrements the counter and pushes the jobs to the faked queue
        Queue::hasPushed(MyJob::class); // true
    }