Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Support asserting against chained batches #49003

Merged
merged 10 commits into from
Nov 17, 2023
24 changes: 18 additions & 6 deletions src/Illuminate/Bus/ChainedBatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,19 @@ public static function prepareNestedBatches(Collection $jobs): Collection
*/
public function handle()
{
$batch = new PendingBatch(Container::getInstance(), $this->jobs);
$this->attachRemainderOfChainToEndOfBatch(
$this->toPendingBatch()
)->dispatch();
}

/**
* Convert the chained batch instance into a pending batch.
*
* @return \Illuminate\Bus\PendingBatch
*/
public function toPendingBatch()
{
$batch = Container::getInstance()->make(Dispatcher::class)->batch($this->jobs);

$batch->name = $this->name;
$batch->options = $this->options;
Expand All @@ -85,8 +97,6 @@ public function handle()
$batch->onConnection($this->connection);
}

$this->dispatchRemainderOfChainAfterBatch($batch);

foreach ($this->chainCatchCallbacks ?? [] as $callback) {
$batch->catch(function (Batch $batch, ?Throwable $exception) use ($callback) {
if (! $batch->allowsFailures()) {
Expand All @@ -95,16 +105,16 @@ public function handle()
});
}

$batch->dispatch();
return $batch;
}

/**
* Move the remainder of the chain to a "finally" batch callback.
*
* @param \Illuminate\Bus\PendingBatch $batch
* @return
* @return \Illuminate\Bus\PendingBatch
*/
protected function dispatchRemainderOfChainAfterBatch(PendingBatch $batch)
protected function attachRemainderOfChainToEndOfBatch(PendingBatch $batch)
{
if (! empty($this->chained)) {
$next = unserialize(array_shift($this->chained));
Expand All @@ -126,5 +136,7 @@ protected function dispatchRemainderOfChainAfterBatch(PendingBatch $batch)

$this->chained = [];
}

return $batch;
}
}
74 changes: 38 additions & 36 deletions src/Illuminate/Support/Testing/Fakes/BusFake.php
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@ public function assertChained(array $expectedChain)

if ($command instanceof Closure) {
[$command, $callback] = [$this->firstClosureParameterType($command), $command];
} elseif ($command instanceof ChainedBatchTruthTest) {
$instance = $command;

$command = ChainedBatch::class;

$callback = fn ($job) => $instance($job->toPendingBatch());
} elseif (! is_string($command)) {
$instance = $command;

Expand All @@ -349,9 +355,7 @@ public function assertChained(array $expectedChain)
"The expected [{$command}] job was not dispatched."
);

$this->isChainOfObjects($expectedChain)
? $this->assertDispatchedWithChainOfObjects($command, $expectedChain, $callback)
: $this->assertDispatchedWithChainOfClasses($command, $expectedChain, $callback);
$this->assertDispatchedWithChainOfObjects($command, $expectedChain, $callback);
}

/**
Expand Down Expand Up @@ -388,7 +392,7 @@ public function assertDispatchedWithoutChain($command, $callback = null)
"The expected [{$command}] job was not dispatched."
);

$this->assertDispatchedWithChainOfClasses($command, [], $callback);
$this->assertDispatchedWithChainOfObjects($command, [], $callback);
}

/**
Expand All @@ -401,48 +405,46 @@ public function assertDispatchedWithoutChain($command, $callback = null)
*/
protected function assertDispatchedWithChainOfObjects($command, $expectedChain, $callback)
{
$chain = collect($expectedChain)->map(fn ($job) => serialize($job))->all();
$chain = $expectedChain;

PHPUnit::assertTrue(
$this->dispatched($command, $callback)->filter(
fn ($job) => $job->chained == $chain
)->isNotEmpty(),
$this->dispatched($command, $callback)->filter(function ($job) use ($chain) {
if (count($chain) !== count($job->chained)) {
return false;
}

foreach ($job->chained as $index => $serializedChainedJob) {
if ($chain[$index] instanceof ChainedBatchTruthTest) {
$chainedBatch = unserialize($serializedChainedJob);

if (! $chainedBatch instanceof ChainedBatch ||
! $chain[$index]($chainedBatch->toPendingBatch())) {
return false;
}
} elseif (is_string($chain[$index])) {
if ($chain[$index] != get_class(unserialize($serializedChainedJob))) {
return false;
}
} elseif (serialize($chain[$index]) != $serializedChainedJob) {
return false;
}
}

return true;
})->isNotEmpty(),
'The expected chain was not dispatched.'
);
}

/**
* Assert if a job was dispatched with chained jobs based on a truth-test callback.
* Create a new assertion about a chained batch.
*
* @param string $command
* @param array $expectedChain
* @param callable|null $callback
* @return void
*/
protected function assertDispatchedWithChainOfClasses($command, $expectedChain, $callback)
{
$matching = $this->dispatched($command, $callback)->map->chained->map(function ($chain) {
return collect($chain)->map(
fn ($job) => get_class(unserialize($job))
);
})->filter(
fn ($chain) => $chain->all() === $expectedChain
);

PHPUnit::assertTrue(
$matching->isNotEmpty(), 'The expected chain was not dispatched.'
);
}

/**
* Determine if the given chain is entirely composed of objects.
*
* @param array $chain
* @return bool
* @param \Closure $callback
* @return \Illuminate\Support\Testing\Fakes\ChainedBatchTruthTest
*/
protected function isChainOfObjects($chain)
public function chainedBatch(Closure $callback)
{
return ! collect($chain)->contains(fn ($job) => ! is_object($job));
return new ChainedBatchTruthTest($callback);
}

/**
Expand Down
37 changes: 37 additions & 0 deletions src/Illuminate/Support/Testing/Fakes/ChainedBatchTruthTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Illuminate\Support\Testing\Fakes;

use Closure;

class ChainedBatchTruthTest
{
/**
* The underlying truth test.
*
* @var \Closure
*/
protected $callback;

/**
* Create a new truth test instance.
*
* @param \Closure $callback
* @return void
*/
public function __construct(Closure $callback)
{
$this->callback = $callback;
}

/**
* Invoke the truth test with the given pending batch.
*
* @param \Illuminate\Bus\PendingBatch
* @return bool
*/
public function __invoke($pendingBatch)
{
return call_user_func($this->callback, $pendingBatch);
}
}
50 changes: 50 additions & 0 deletions tests/Support/SupportTestingBusFakeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Illuminate\Bus\Batch;
use Illuminate\Bus\Queueable;
use Illuminate\Container\Container;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Contracts\Bus\QueueingDispatcher;
use Illuminate\Support\Testing\Fakes\BatchRepositoryFake;
use Illuminate\Support\Testing\Fakes\BusFake;
Expand Down Expand Up @@ -468,6 +470,10 @@ public function testAssertNothingDispatched()

public function testAssertChained()
{
Container::setInstance($container = new Container);

$container->instance(Dispatcher::class, $this->fake);

$this->fake->chain([
new ChainedJobStub,
])->dispatch();
Expand All @@ -485,6 +491,50 @@ public function testAssertChained()
ChainedJobStub::class,
OtherBusJobStub::class,
]);

$this->fake->chain([
new ChainedJobStub,
$this->fake->batch([
new OtherBusJobStub,
new OtherBusJobStub,
]),
new ChainedJobStub,
])->dispatch();

$this->fake->assertChained([
ChainedJobStub::class,
$this->fake->chainedBatch(function ($pendingBatch) {
return $pendingBatch->jobs->count() === 2;
}),
ChainedJobStub::class,
]);

$this->fake->assertChained([
new ChainedJobStub,
$this->fake->chainedBatch(function ($pendingBatch) {
return $pendingBatch->jobs->count() === 2;
}),
new ChainedJobStub,
]);

$this->fake->chain([
$this->fake->batch([
new OtherBusJobStub,
new OtherBusJobStub,
]),
new ChainedJobStub,
new ChainedJobStub,
])->dispatch();

$this->fake->assertChained([
$this->fake->chainedBatch(function ($pendingBatch) {
return $pendingBatch->jobs->count() === 2;
}),
ChainedJobStub::class,
ChainedJobStub::class,
]);

Container::setInstance(null);
}

public function testAssertDispatchedWithIgnoreClass()
Expand Down