Skip to content
Open
Show file tree
Hide file tree
Changes from 40 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
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ services:
- 443:443
volumes:
- ./:/app
extra_hosts:
- "host.docker.internal:host-gateway"
11 changes: 8 additions & 3 deletions src/Traits/FixturesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ public function assertEqualsFixture(string $fixture, $data, bool $exportMode = f
* current version is selected; if none match, falls back to $fixture as-is.
*/
public function assertEqualsVersionedFixture(string $fixture, $data, array $versions = [], bool $exportMode = false): void
{
$fixture = $this->getVersioningFixtureName($fixture, $versions);

$this->assertEqualsFixture($fixture, $data, $exportMode);
}

protected function getVersioningFixtureName(string $fixture, array $versions = []): string
{
$currentVersion = (int) app()->version();
list($filename, $directory) = extract_last_part($fixture, DIRECTORY_SEPARATOR);
Expand All @@ -145,11 +152,9 @@ public function assertEqualsVersionedFixture(string $fixture, $data, array $vers
}
}

$finalFixture = (is_null($fixtureVersion))
return (is_null($fixtureVersion))
? $fixture
: "{$prefix}laravel_before_v{$fixtureVersion}/{$filename}";

$this->assertEqualsFixture($finalFixture, $data, $exportMode);
}

public function exportJson($fixture, $data): void
Expand Down
24 changes: 24 additions & 0 deletions src/Traits/ReflectionTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace RonasIT\Support\Traits;

use ReflectionClass;

trait ReflectionTrait
{
protected function getObjectAttributes(object $object): array
{
$reflection = new ReflectionClass($object);
$attributes = [];

foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || !$property->isInitialized($object)) {
continue;
}

$attributes[$property->getName()] = $property->getValue($object);
}

return $attributes;
}
}
45 changes: 45 additions & 0 deletions src/Traits/TestingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

namespace RonasIT\Support\Traits;

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

trait TestingTrait
{
use FixturesTrait;
use MailsMockTrait;
use MockTrait;
use ReflectionTrait;

protected function assertExceptionThrew(string $expectedClassName, string $expectedMessage, bool $isStrict = true): void
{
Expand All @@ -18,4 +22,45 @@ protected function assertExceptionThrew(string $expectedClassName, string $expec

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

public function assertQueueEqualsVersioningFixture(string $fixture, array $versions = [], bool $exportMode = false): void
{
$fixture = $this->getVersioningFixtureName($fixture, $versions);

$this->assertQueueEqualsFixture($fixture, $exportMode);
}

public function assertQueueEqualsFixture(string $fixture, bool $exportMode = false): void
{
$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 👍 / 👎.

});
}

$actualData = json_decode(json_encode($actualData), true);

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 nested object state in queue snapshots

When a queued job has an attribute that is itself a DTO/value object with private or protected properties, getObjectAttributes() returns that nested object but this JSON round-trip serializes only public/JsonSerializable state. For example, two different payload objects with only private fields both become {}, so assertQueueEqualsFixture() can keep passing while the actual queued payload state changes; recursively normalize nested objects or fail loudly before encoding.

Useful? React with 👍 / 👎.


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

protected function assertQueueEmpty(): void
{
$this->assertEquals([], Queue::pushedJobs(), 'Failed assert that faked queue is empty.');
}

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.
}
}
63 changes: 63 additions & 0 deletions tests/TestingTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +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\Traits\TestingTrait;

class TestingTraitTest extends TestCase
{
use TestingTrait;

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
{
$this->assertExceptionThrew(ModelFactoryNotFound::class, 'full error message');
Expand All @@ -26,4 +42,51 @@ 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->assertQueueEqualsVersioningFixture('assert_queue_equals');

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 Pass version boundaries to queue fixture assertions

Fresh evidence beyond the earlier versioning concern is that the commit now adds laravel_before_v11/laravel_before_v12 queue fixture directories, but this call and the three below still omit the $versions argument, so getVersioningFixtureName() receives [] and always compares against the root fixture. When the suite runs on a supported older framework version such as Laravel 11 (composer.json allows laravel/framework >=11.0), those versioned fixtures are never selected and the queue tests compare against snapshots containing newer queue properties instead of the Laravel 11 shape.

Useful? React with 👍 / 👎.

}

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

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

$this->assertQueueEqualsVersioningFixture('assert_queue_equals_as_class_name_with_params');
}

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

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

$this->assertQueueEqualsVersioningFixture('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->assertQueueEqualsVersioningFixture('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,24 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"tries": 5,
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"debounceOwner": "",
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 Version debounce fixtures by minor release

Fresh evidence beyond the earlier major-version concern is that the new [12, 13] selection still treats every Laravel 13 install as using this root fixture. debounceOwner was added with Laravel's debounced queue jobs in the 13.6 line, while composer.json allows any laravel/framework >=11.0; on supported Laravel 13.0–13.5 installs the reflected job does not have this property, so these queue fixture tests compare against an extra key and fail unless the fixture selection accounts for the minor boundary or avoids this framework-internal field.

Useful? React with 👍 / 👎.

"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,22 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"payload": "some payload",
"anotherPayload": [],
"tries": 5,
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"debounceOwner": "",
"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,24 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"tries": 5,
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"debounceOwner": "",
"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,42 @@
{
"RonasIT\\Support\\Tests\\Support\\Mock\\Jobs\\TestJob": [
{
"payload": "some payload",
"anotherPayload": [
"another payload"
],
"tries": 5,
"job": null,
"connection": null,
"queue": "some_queue",
"messageGroup": null,
"deduplicator": null,
"debounceOwner": "",
"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,
"messageGroup": null,
"deduplicator": null,
"debounceOwner": "",
"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": "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
}
]
}
Loading
Loading