Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
44f823f
feat: implement queue assertion helpers
AZabolotnikov May 20, 2026
4aa6260
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 20, 2026
b5c32a3
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 20, 2026
4e42e4a
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 21, 2026
a6d3519
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 21, 2026
ea0f94d
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 21, 2026
d6073ec
Merge branch 'master' into Implement-queue-assertion-helpers
AZabolotnikov May 21, 2026
151527e
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 21, 2026
e6f5df4
Merge remote-tracking branch 'origin/Implement-queue-assertion-helper…
AZabolotnikov May 21, 2026
85ba290
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 21, 2026
eab1d55
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
2df9b17
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
5a5a790
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
5c010b9
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
7a1d4a1
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
b7e5c3c
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
a88892f
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
55bf96b
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
d8d60be
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
4a59481
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
019ae09
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
59fff99
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
088de9a
feat: add queue assertion helpers and test job for verifying queue st…
AZabolotnikov May 22, 2026
2d18ac1
Merge branch 'master' into Implement-queue-assertion-helpers
AZabolotnikov Jun 4, 2026
47f8fb8
fix: strict change detection in TableTestState
AZabolotnikov Jun 4, 2026
a4cc42b
fix: strict change detection in TableTestState
AZabolotnikov Jun 4, 2026
fa52d8a
fix: strict change detection in TableTestState
AZabolotnikov Jun 4, 2026
660fefe
Merge branch 'master' into Implement-queue-assertion-helpers
DenTray Jun 16, 2026
d09cb4a
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
5aa6369
Merge remote-tracking branch 'origin/Implement-queue-assertion-helper…
AZabolotnikov Jun 22, 2026
444ce8f
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
eb347e7
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
5ed9bbf
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
9477cd8
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
db9e1c4
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
ad57ad5
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
6c8111d
fix: strict change detection in TableTestState
AZabolotnikov Jun 22, 2026
335db99
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
4dd36dc
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
40aeee9
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
8189338
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
41b4fce
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
4303612
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
1c9b862
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
cd5242e
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
3b53ae1
feat: implement queue assertion helpers
AZabolotnikov Jun 23, 2026
2e4d0d1
Merge branch 'master' into Implement-queue-assertion-helpers
AZabolotnikov Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/Traits/TestingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

namespace RonasIT\Support\Traits;

use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Queue;
use ReflectionClass;

trait TestingTrait
{
use FixturesTrait;
Expand All @@ -18,4 +22,51 @@ protected function assertExceptionThrew(string $expectedClassName, string $expec

$this->expectExceptionMessageMatches("/{$expectedMessage}/");
}

protected function assertQueueEqualsFixture(string $fixture, bool $exportMode = false): void
Comment thread
AZabolotnikov marked this conversation as resolved.
Outdated
{
$actualData = [];

foreach (Queue::pushedJobs() as $namespace => $jobs) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve cross-class dispatch order in snapshots

When jobs of different classes are dispatched to the same queue, iterating Queue::pushedJobs() snapshots class-keyed buckets instead of the chronological queue sequence, so sequences such as A, B, A and A, A, B normalize to the same fixture as long as each class bucket is unchanged. Since the real queue processes jobs in push order, tests using this helper can miss regressions that reorder different job classes; include a flat sequence/index in the captured data rather than only class-keyed buckets.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include raw pushes in queue snapshots

When code under test calls Queue::pushRaw() alongside normal queued jobs, Laravel's fake records those payloads separately from pushedJobs(). Because this snapshot is built only from pushedJobs(), an unexpected raw payload is invisible and assertQueueEqualsFixture() can still pass against a fixture that matches only the object jobs; include raw pushes in the captured data or fail when any are present.

Useful? React with 👍 / 👎.

$actualData[$namespace] = Arr::map($jobs, function ($job) {
$job = $this->getJobObject($job);

return $this->getObjectAttributes($job);
Comment thread
AZabolotnikov marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve captured state for queued closures

When a test queues a closure job such as Queue::push(function () use ($id) {}), this path passes the Closure into getObjectAttributes(), but closures expose no reflected properties, so every closure snapshots as an empty object. A fixture containing one closure will therefore keep passing even if the captured variables or queued work change; snapshot closure static variables with ReflectionFunction or reject closure jobs explicitly.

Useful? React with 👍 / 👎.

});
}

$this->assertEqualsFixture("queue_states/{$fixture}", $actualData, $exportMode);
}

protected function getJobObject(array $job): object
{
if (is_object($job['job'])) {
return $job['job'];
}

$data = Arr::wrap($job['data']);
$className = $job['job'];

return new $className(...$data);
Comment thread
AZabolotnikov marked this conversation as resolved.
}

protected function assertQueueEmpty(): void
Comment thread
AZabolotnikov marked this conversation as resolved.
Outdated
{
$this->assertEquals([], Queue::pushedJobs(), 'Failed assert that faked queue is empty.');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include raw pushes when asserting an empty queue

When code under test uses Laravel's raw queue API (Queue::pushRaw(...)) while the queue is faked, QueueFake stores those entries in rawPushes() rather than pushedJobs(). This assertion therefore passes even though the fake queue contains raw payloads, so tests can miss unexpected raw jobs; check both pushed jobs and raw pushes (or delegate to the fake's empty assertion) before reporting the queue as empty.

Useful? React with 👍 / 👎.

}

protected function getObjectAttributes(object $object): array
Comment thread
AZabolotnikov marked this conversation as resolved.
Outdated
{
$result = [];

$properties = (new ReflectionClass($object))->getProperties();
Comment thread
AZabolotnikov marked this conversation as resolved.
Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include inherited private properties in snapshots

For jobs that extend a base class with private instance state, ReflectionClass($object)->getProperties() on the concrete job omits the parent class's private properties, so assertQueueEqualsFixture() can pass even when queued state stored in that base class changes. This affects any queued job that keeps constructor or routing data in private parent properties; walk the parent reflection chain or otherwise include those properties so the fixture covers the full object state.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Snapshot method-defined queue options

When tests rely on this helper to catch changes to queue retry behavior, this path only serializes reflected properties, so jobs that define queue options through methods or attributes (for example backoff(), retryUntil(), or Laravel 13 #[Backoff] / #[Tries]) keep passing even when their actual retry timing or attempt limits change. The new BaseTestJob::backoff() used by these fixtures is already omitted for this reason; include method/attribute-derived queue options in the snapshot so the fixture represents the queued job behavior.

Useful? React with 👍 / 👎.


foreach ($properties as $property) {
$result[$property->getName()] = $property->isInitialized($object)
? $property->getValue($object)
: null;
}

return json_decode(json_encode($result), true);
}
}
75 changes: 74 additions & 1 deletion tests/TestingTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@

namespace RonasIT\Support\Tests;

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;
use RonasIT\Support\Exceptions\ModelFactoryNotFound;
use RonasIT\Support\Tests\Support\Mock\Jobs\AnotherTestJob;
use RonasIT\Support\Tests\Support\Mock\Jobs\TestJob;
use RonasIT\Support\Tests\Support\Traits\TestingTraitTestTrait;
use RonasIT\Support\Traits\TestingTrait;

class TestingTraitTest extends TestCase
{
use TestingTrait;
use TestingTrait, TestingTraitTestTrait;

public static int $laravelMajorVersion;

public function setUp(): void
{
parent::setUp();

Carbon::setTestNow('2020-01-01');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset Carbon test clock after these tests

setUp() freezes Carbon globally with Carbon::setTestNow('2020-01-01'), but this class never clears it, so later test classes can inherit the frozen clock and get incorrect now() / Carbon::now() values depending on execution order. This introduces cross-test state leakage and flaky failures whenever subsequent tests rely on real current time; add a teardown reset (for example Carbon::setTestNow(null)) to keep tests isolated.

Useful? React with 👍 / 👎.


self::$laravelMajorVersion ??= (int) Str::before($this->app->version(), '.');
}

public function testAssertExceptionThrew(): void
{
Expand All @@ -26,4 +43,60 @@ public function testAssertExceptionThrewNotStrictly(): void

throw new ModelFactoryNotFound('full error message');
}

public function testAssertQueueEqualsFixture(): void
{
Queue::fake();

TestJob::dispatch('some payload', ['another payload'])->delay(now()->addMinute());

$this->assertQueueEqualsVersionedFixture(self::$laravelMajorVersion, 'assert_queue_equals');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use

public function assertEqualsVersionedFixture(string $fixture, $data, array $versions = [], bool $exportMode = false): void

}

public function testAssertQueueEqualsFixturePushAsClassName(): void
{
Queue::fake();

Queue::push(TestJob::class);

$this->assertQueueEqualsVersionedFixture(self::$laravelMajorVersion, 'assert_queue_equals_as_class_name');
}

public function testAssertQueueEqualsFixturePushAsStringWithParams(): void
{
Queue::fake();

Queue::push(TestJob::class, [
'payload' => 'some payload',
'anotherPayload' => ['another payload'],
]);

$this->assertQueueEqualsVersionedFixture(self::$laravelMajorVersion, 'assert_queue_equals_as_class_name_with_params');
}

public function testAssertQueueEqualsFixturePushAsStringWithOneParam(): void
{
Queue::fake();

Queue::push(TestJob::class, 'some payload');

$this->assertQueueEqualsVersionedFixture(self::$laravelMajorVersion, 'assert_queue_equals_as_class_name_with_one_param');
}

public function testAssertQueueEqualsFixtureDifferentJobs(): void
{
Queue::fake();

TestJob::dispatch('some payload', ['another payload']);
AnotherTestJob::dispatch('some payload', ['another payload']);

$this->assertQueueEqualsVersionedFixture(self::$laravelMajorVersion, 'assert_queue_equals_different_jobs');
}

public function testAssertQueueEmpty()
{
Queue::fake();

$this->assertQueueEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"job": null,
"connection": null,
"queue": "some_queue",
"delay": "2020-01-01T00:01:00.000000Z",
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "",
"anotherPayload": [],
"job": null,
"connection": null,
"queue": "some_queue",
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "some payload",
"anotherPayload": [],
"job": null,
"connection": null,
"queue": "some_queue",
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"job": null,
"connection": null,
"queue": "some_queue",
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"job": null,
"connection": null,
"queue": "some_queue",
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
],
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\AnotherTestJob": [
{
"tries": 5,
"job": null,
"connection": null,
"queue": null,
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"delay": "2020-01-01T00:01:00.000000Z",
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "",
"anotherPayload": [],
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "some payload",
"anotherPayload": [],
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"tries": 5,
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"delay": null,
"afterCommit": null,
"middleware": [],
"chained": [],
"chainConnection": null,
"chainQueue": null,
"chainCatchCallbacks": null
}
]
}
Loading