Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bwrice's avatar
Level 11

Anyone know a good way to test "then" logic with Laravel 8's new queue batching?

If I have some code like this:

        Bus::batch([
            new FinalizeOrder,
            new FinalizeOrder,
            new FinalizeOrder
        ])->then(function () {
            CreateOrderReport::dispatch();
        })->dispatch();

I want to assert the CreateOrderReport was queued, but when I use Queue::fake(), "then()" never runs because the batched jobs aren't actually processed.

Anyone using Laravel 8's new job batching feature and have any insight on how you're testing logic inside the callbacks?

0 likes
5 replies
martinbean's avatar

@bwrice I wouldn’t test this, because then you’re testing the framework. It’s Laravel that handles processing jobs and then calling any “then” callbacks; not your application.

bwrice's avatar
Level 11

@martinbean I would still like to test the logic inside the "then" callback. Perhaps, in the future, CreateOrderReport has a dependency I need to pass into the constructor. If that were the case, I would have no test coverage that failed for this code.

martinbean's avatar

@bwrice Oh, no. You’d absolutely still have a test for your CreateOrderReport job class. I just mean that you wouldn’t test the bus, because that will already be tested by Laravel.

Soyhuce's avatar

This question is a bit old but I just found it and had the same problem.

@martinbean bwrice wants to test

function () {
    CreateOrderReport::dispatch();
}

and be sure it works correctly. It does not have anything to do with the framework.

@bwrice this is a bit hacky but is enough for what I'm doing. I hope it will help you too.

First, you will need a BatchFake class :

use Carbon\CarbonImmutable;
use Illuminate\Bus\Batch;
use Illuminate\Bus\UpdatedBatchJobCounts;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\Str;
use Illuminate\Support\Testing\Fakes\BatchRepositoryFake;
use Illuminate\Support\Testing\Fakes\PendingBatchFake;
use Illuminate\Support\Testing\Fakes\QueueFake;

class BatchFake extends Batch
{
    public static function make(PendingBatchFake $batch): BatchFake
    {
        return new self(
            new QueueFake(Facade::getFacadeApplication()),
            new BatchRepositoryFake(),
            (string) Str::orderedUuid(),
            $batch->name,
            count($batch->jobs),
            count($batch->jobs),
            0,
            [],
            $batch->options,
            CarbonImmutable::now(),
            null,
            null
        );
    }

    public function fresh(): self
    {
        return $this;
    }

    public function incrementFailedJobs(string $jobId): UpdatedBatchJobCounts
    {
        return new UpdatedBatchJobCounts(0, 1);
    }
}

Then, in your test, you can "simply" do something like this :

Bus::fake();

$this->executeSomeCodeStartingABatch();

Bus::assertBatched(function (PendingBatchFake $batch) {
    // the job id is irrelevant here, put what you want
    BatchFake::make($batch)->recordSuccessfulJob('job_id');

    return true;
});

// here, the `->then()` and `->finally()` callbacks are executed
// and ready to be tested. for example : 
Bus::assertDispatched(CreateOrderReport::class);

Using the same system, you can also test the ->catch() callbacks.

Bus::fake();

$this->executeSomeCodeStartingABatch();

Bus::assertBatched(function (PendingBatchFake $batch) {
    // the job id is irrelevant here, put what you want
    BatchFake::make($batch)->recordFailedJob('job_id', new Exception('Oups!')); 

    return true;
});

// here, the `->catch()` and `->finally()` callbacks are executed
// and ready to be tested. for example : 
Bus::assertNotDispatched(CreateOrderReport::class);

Hope it will help !

coderjono's avatar

I know this question is 8 months old when I reply. But I'd like to post it here, hope this would help someone who is doing some research about this.

To test the closure is pretty simple now as the example below:

Bus::assertBatched(function (PendingBatch $batch) {
    /* @var \Illuminate\Queue\SerializableClosure $catchCallback */
    [$catchCallback] = $batch->catchCallbacks();

    /* @var \Illuminate\Queue\SerializableClosure $thenCallback */
    [$thenCallback] = $batch->thenCallbacks();

    // Make sure the context provides the same variable you need in your closure
    $catchCallback->getClosure()->call($this);
    $thenCallback->getClosure()->call($this);

    // Assuming you make some database changes in the closure that you want to test
    $this->assertDatabaseHas(
        $myModel->getTable(),
        [
            'id' => $myModel->id,
            'value' => 'NEW_VALUE',
        ],
    );

    // Make sure you test the batch is dispatched
    return $batch->name === 'BATCH_NAME';
});
14 likes

Please or to participate in this conversation.