Skip to content

Commit a963567

Browse files
authored
Support for persistent Bus + Queue fake
2 parents d174ba8 + 1982ce4 commit a963567

20 files changed

+723
-25
lines changed

.github/workflows/run-tests.yml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,13 @@ jobs:
1212
strategy:
1313
fail-fast: true
1414
matrix:
15-
php: [8.2, 8.1, 8.0]
16-
laravel: [10.*, 9.*]
15+
php: [8.2, 8.1]
16+
laravel: [10.*]
1717
os: [ubuntu-latest, windows-latest]
1818
stability: [prefer-lowest, prefer-stable]
1919
include:
2020
- laravel: 10.*
2121
testbench: 8.*
22-
- laravel: 9.*
23-
testbench: 7.*
24-
exclude:
25-
- laravel: 10.*
26-
php: 8.0
2722

2823
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }}
2924

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,55 @@ You can install the package via composer:
2323
composer require protonemedia/laravel-dusk-fakes --dev
2424
```
2525

26+
### Persist Bus (queued jobs)
27+
28+
Make sure you've set the `DUSK_FAKE_BUS` environment variable to `true` in the [Dusk environment](https://laravel.com/docs/9.x/dusk#environment-handling).
29+
30+
Finally, add the `PersistentBus` trait to your test. You don't have to manually call the `fake()` method on the `Bus` facade.
31+
32+
```php
33+
<?php
34+
35+
namespace Tests\Browser\Auth;
36+
37+
use App\Jobs\SendOrderInvoice;
38+
use App\Models\Order;
39+
use Illuminate\Foundation\Testing\DatabaseMigrations;
40+
use Illuminate\Support\Facades\Mail;
41+
use Laravel\Dusk\Browser;
42+
use ProtoneMedia\LaravelDuskFakes\Bus\PersistentBus;
43+
use Tests\DuskTestCase;
44+
45+
class OrderInvoiceTest extends DuskTestCase
46+
{
47+
use DatabaseMigrations;
48+
use PersistentBus;
49+
50+
public function test_dispatch_invoice_job_after_confirming_order()
51+
{
52+
$this->browse(function (Browser $browser) {
53+
$order = Order::factory()->create();
54+
55+
$browser->visit('/order/'.$order->id)
56+
->press('Confirm')
57+
->waitForText('We will generate an invoice!');
58+
59+
Bus::assertDispatched(SendOrderInvoice::class);
60+
});
61+
}
62+
}
63+
```
64+
65+
If you only need to fake specific jobs while allowing your other jobs to execute normally, you may pass the class names of the jobs that should be faked to the `jobsToFake()` method:
66+
67+
```php
68+
Bus::jobsToFake(ShipOrder::class);
69+
70+
$browser->visit(...);
71+
72+
Bus::assertDispatched(SendOrderInvoice::class);
73+
```
74+
2675
### Persist Mails
2776

2877
Make sure you've set the `DUSK_FAKE_MAILS` environment variable to `true` in the [Dusk environment](https://laravel.com/docs/9.x/dusk#environment-handling).
@@ -104,6 +153,55 @@ class PasswordResetTest extends DuskTestCase
104153
}
105154
```
106155

156+
### Persist Queue
157+
158+
Make sure you've set the `DUSK_FAKE_QUEUE` environment variable to `true` in the [Dusk environment](https://laravel.com/docs/9.x/dusk#environment-handling).
159+
160+
Finally, add the `PersistentQueue` trait to your test. You don't have to manually call the `fake()` method on the `Queue` facade.
161+
162+
```php
163+
<?php
164+
165+
namespace Tests\Browser\Auth;
166+
167+
use App\Jobs\SendOrderInvoice;
168+
use App\Models\Order;
169+
use Illuminate\Foundation\Testing\DatabaseMigrations;
170+
use Illuminate\Support\Facades\Mail;
171+
use Laravel\Dusk\Browser;
172+
use ProtoneMedia\LaravelDuskFakes\Queue\PersistentQueue;
173+
use Tests\DuskTestCase;
174+
175+
class OrderInvoiceTest extends DuskTestCase
176+
{
177+
use DatabaseMigrations;
178+
use PersistentQueue;
179+
180+
public function test_dispatch_invoice_job_after_confirming_order()
181+
{
182+
$this->browse(function (Browser $browser) {
183+
$order = Order::factory()->create();
184+
185+
$browser->visit('/order/'.$order->id)
186+
->press('Confirm')
187+
->waitForText('We will generate an invoice!');
188+
189+
Queue::assertDispatched(SendOrderInvoice::class);
190+
});
191+
}
192+
}
193+
```
194+
195+
If you only need to fake specific jobs while allowing your other jobs to execute normally, you may pass the class names of the jobs that should be faked to the `jobsToFake()` method:
196+
197+
```php
198+
Queue::jobsToFake(ShipOrder::class);
199+
200+
$browser->visit(...);
201+
202+
Queue::assertDispatched(SendOrderInvoice::class);
203+
```
204+
107205
## Changelog
108206

109207
Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.

composer.json

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,16 @@
1616
}
1717
],
1818
"require": {
19-
"php": "^8.0|^8.1|^8.2",
20-
"illuminate/contracts": "^9.0|^10.0",
21-
"laravel/dusk": "^7.0"
22-
},
23-
"conflict": {
24-
"laravel/framework": "<9.15.0"
19+
"php": "^8.1|^8.2",
20+
"illuminate/contracts": "^10.0",
21+
"laravel/dusk": "^7.0",
22+
"spatie/invade": "^1.1"
2523
},
2624
"require-dev": {
2725
"laravel/pint": "^1.0",
2826
"nesbot/carbon": "^2.66",
2927
"nunomaduro/collision": "^6.0",
30-
"orchestra/testbench": "^7.0|^8.0",
28+
"orchestra/testbench": "^8.0",
3129
"pestphp/pest": "^1.21",
3230
"pestphp/pest-plugin-laravel": "^1.1",
3331
"phpunit/phpunit": "^9.5|^10.0"
@@ -63,4 +61,4 @@
6361
},
6462
"minimum-stability": "dev",
6563
"prefer-stable": true
66-
}
64+
}

config/dusk-fakes.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<?php
22

33
return [
4+
'bus' => [
5+
'enabled' => env('DUSK_FAKE_BUS', false),
6+
'storage_root' => storage_path('framework/testing/bus'),
7+
],
8+
49
'mails' => [
510
'enabled' => env('DUSK_FAKE_MAILS', false),
611
'storage_root' => storage_path('framework/testing/mails'),
@@ -10,4 +15,9 @@
1015
'enabled' => env('DUSK_FAKE_NOTIFICATIONS', false),
1116
'storage_root' => storage_path('framework/testing/notifications'),
1217
],
18+
19+
'queue' => [
20+
'enabled' => env('DUSK_FAKE_QUEUE', false),
21+
'storage_root' => storage_path('framework/testing/queue'),
22+
],
1323
];

src/Bus/PersistentBus.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace ProtoneMedia\LaravelDuskFakes\Bus;
4+
5+
use Illuminate\Support\Facades\Bus;
6+
7+
trait PersistentBus
8+
{
9+
public function setUpPersistentBus()
10+
{
11+
Bus::swap(
12+
app(UncachedPersistentBusFake::class)
13+
);
14+
}
15+
16+
public function tearDownPersistentBus()
17+
{
18+
Bus::cleanStorage();
19+
}
20+
}

src/Bus/PersistentBusFake.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
namespace ProtoneMedia\LaravelDuskFakes\Bus;
4+
5+
use Illuminate\Bus\PendingBatch;
6+
use Illuminate\Contracts\Bus\QueueingDispatcher;
7+
use Illuminate\Filesystem\Filesystem;
8+
use Illuminate\Support\Arr;
9+
use Illuminate\Support\Testing\Fakes\BusFake;
10+
use Illuminate\Support\Testing\Fakes\PendingBatchFake;
11+
12+
class PersistentBusFake extends BusFake
13+
{
14+
private string $directory;
15+
16+
private string $storage;
17+
18+
public function __construct(QueueingDispatcher $dispatcher, $jobsToFake = [])
19+
{
20+
parent::__construct($dispatcher, $jobsToFake);
21+
22+
$this->directory = rtrim(config('dusk-fakes.bus.storage_root'), '/');
23+
24+
$this->storage = $this->directory.'/serialized';
25+
26+
(new Filesystem)->ensureDirectoryExists($this->directory);
27+
28+
$this->loadBus();
29+
}
30+
31+
public function jobsToFake($jobsToFake = [])
32+
{
33+
$this->jobsToFake = Arr::wrap($jobsToFake);
34+
35+
$this->storeBus();
36+
}
37+
38+
public function cleanStorage()
39+
{
40+
(new Filesystem)->cleanDirectory($this->directory);
41+
}
42+
43+
public function loadBus(): self
44+
{
45+
$unserialized = file_exists($this->storage)
46+
? rescue(fn () => unserialize(file_get_contents($this->storage)), [], false)
47+
: [];
48+
49+
$this->jobsToFake = $unserialized['jobsToFake'] ?? [];
50+
$this->commands = $unserialized['commands'] ?? [];
51+
$this->commandsSync = $unserialized['commandsSync'] ?? [];
52+
$this->commandsAfterResponse = $unserialized['commandsAfterResponse'] ?? [];
53+
$this->batches = $unserialized['batches'] ?? [];
54+
55+
return $this;
56+
}
57+
58+
public function dispatch($command)
59+
{
60+
return tap(parent::dispatch($command), fn () => $this->storeBus());
61+
}
62+
63+
public function dispatchSync($command, $handler = null)
64+
{
65+
return tap(parent::dispatchSync($command, $handler), fn () => $this->storeBus());
66+
}
67+
68+
public function dispatchNow($command, $handler = null)
69+
{
70+
return tap(parent::dispatchNow($command, $handler), fn () => $this->storeBus());
71+
}
72+
73+
public function dispatchToQueue($command)
74+
{
75+
return tap(parent::dispatchToQueue($command), fn () => $this->storeBus());
76+
}
77+
78+
public function dispatchAfterResponse($command)
79+
{
80+
return tap(parent::dispatchAfterResponse($command), fn () => $this->storeBus());
81+
}
82+
83+
public function recordPendingBatch(PendingBatch $pendingBatch)
84+
{
85+
return tap(parent::recordPendingBatch($pendingBatch), fn () => $this->storeBus());
86+
}
87+
88+
public function cleanupCommand(array $jobs): array
89+
{
90+
return collect($jobs)->map(function ($job) {
91+
tap(invade($job), function ($job) {
92+
if (! $job->job) {
93+
return;
94+
}
95+
96+
$job = invade($job->job);
97+
$job->container = null;
98+
99+
if (! $job->instance) {
100+
return;
101+
}
102+
103+
invade($job->instance)->container = null;
104+
invade($job->instance)->dispatcher = null;
105+
});
106+
107+
return $job;
108+
})->all();
109+
}
110+
111+
private function storeBus()
112+
{
113+
(new Filesystem)->ensureDirectoryExists($this->directory);
114+
115+
file_put_contents($this->storage, serialize([
116+
'jobsToFake' => $this->jobsToFake,
117+
'commands' => collect($this->commands)->map([$this, 'cleanupCommand'])->all(),
118+
'commandsSync' => collect($this->commandsSync)->map([$this, 'cleanupCommand'])->all(),
119+
'commandsAfterResponse' => collect($this->commandsAfterResponse)->map([$this, 'cleanupCommand'])->all(),
120+
'batches' => collect($this->batches)->each(function (PendingBatchFake $batch) {
121+
tap(invade($batch), function ($batch) {
122+
$batch->bus = null;
123+
});
124+
125+
return $batch;
126+
})->all(),
127+
]));
128+
}
129+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace ProtoneMedia\LaravelDuskFakes\Bus;
4+
5+
use Illuminate\Support\Traits\ForwardsCalls;
6+
7+
/**
8+
* @mixin \Illuminate\Support\Testing\Fakes\BusFake
9+
*/
10+
class UncachedPersistentBusFake
11+
{
12+
use ForwardsCalls;
13+
14+
public function __construct(private PersistentBusFake $fake)
15+
{
16+
}
17+
18+
/**
19+
* Handle dynamic method calls into the fake.
20+
*
21+
* @param string $method
22+
* @param array $parameters
23+
* @return mixed
24+
*/
25+
public function __call($method, $parameters)
26+
{
27+
return $this->forwardCallTo($this->fake->loadBus(), $method, $parameters);
28+
}
29+
30+
/**
31+
* Handle dynamic static method calls into the fake.
32+
*
33+
* @param string $method
34+
* @param array $parameters
35+
* @return mixed
36+
*/
37+
public static function __callStatic($method, $parameters)
38+
{
39+
return app(static::class)->$method(...$parameters);
40+
}
41+
}

0 commit comments

Comments
 (0)