Skip to content

Commit

Permalink
Support for persistent Bus + Queue fake
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbaljet authored Apr 4, 2023
2 parents d174ba8 + 1982ce4 commit a963567
Show file tree
Hide file tree
Showing 20 changed files with 723 additions and 25 deletions.
9 changes: 2 additions & 7 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,13 @@ jobs:
strategy:
fail-fast: true
matrix:
php: [8.2, 8.1, 8.0]
laravel: [10.*, 9.*]
php: [8.2, 8.1]
laravel: [10.*]
os: [ubuntu-latest, windows-latest]
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 10.*
testbench: 8.*
- laravel: 9.*
testbench: 7.*
exclude:
- laravel: 10.*
php: 8.0

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

Expand Down
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,55 @@ You can install the package via composer:
composer require protonemedia/laravel-dusk-fakes --dev
```

### Persist Bus (queued jobs)

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).

Finally, add the `PersistentBus` trait to your test. You don't have to manually call the `fake()` method on the `Bus` facade.

```php
<?php

namespace Tests\Browser\Auth;

use App\Jobs\SendOrderInvoice;
use App\Models\Order;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\Mail;
use Laravel\Dusk\Browser;
use ProtoneMedia\LaravelDuskFakes\Bus\PersistentBus;
use Tests\DuskTestCase;

class OrderInvoiceTest extends DuskTestCase
{
use DatabaseMigrations;
use PersistentBus;

public function test_dispatch_invoice_job_after_confirming_order()
{
$this->browse(function (Browser $browser) {
$order = Order::factory()->create();

$browser->visit('/order/'.$order->id)
->press('Confirm')
->waitForText('We will generate an invoice!');

Bus::assertDispatched(SendOrderInvoice::class);
});
}
}
```

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:

```php
Bus::jobsToFake(ShipOrder::class);

$browser->visit(...);

Bus::assertDispatched(SendOrderInvoice::class);
```

### Persist Mails

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).
Expand Down Expand Up @@ -104,6 +153,55 @@ class PasswordResetTest extends DuskTestCase
}
```

### Persist Queue

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).

Finally, add the `PersistentQueue` trait to your test. You don't have to manually call the `fake()` method on the `Queue` facade.

```php
<?php

namespace Tests\Browser\Auth;

use App\Jobs\SendOrderInvoice;
use App\Models\Order;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\Mail;
use Laravel\Dusk\Browser;
use ProtoneMedia\LaravelDuskFakes\Queue\PersistentQueue;
use Tests\DuskTestCase;

class OrderInvoiceTest extends DuskTestCase
{
use DatabaseMigrations;
use PersistentQueue;

public function test_dispatch_invoice_job_after_confirming_order()
{
$this->browse(function (Browser $browser) {
$order = Order::factory()->create();

$browser->visit('/order/'.$order->id)
->press('Confirm')
->waitForText('We will generate an invoice!');

Queue::assertDispatched(SendOrderInvoice::class);
});
}
}
```

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:

```php
Queue::jobsToFake(ShipOrder::class);

$browser->visit(...);

Queue::assertDispatched(SendOrderInvoice::class);
```

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
Expand Down
14 changes: 6 additions & 8 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,16 @@
}
],
"require": {
"php": "^8.0|^8.1|^8.2",
"illuminate/contracts": "^9.0|^10.0",
"laravel/dusk": "^7.0"
},
"conflict": {
"laravel/framework": "<9.15.0"
"php": "^8.1|^8.2",
"illuminate/contracts": "^10.0",
"laravel/dusk": "^7.0",
"spatie/invade": "^1.1"
},
"require-dev": {
"laravel/pint": "^1.0",
"nesbot/carbon": "^2.66",
"nunomaduro/collision": "^6.0",
"orchestra/testbench": "^7.0|^8.0",
"orchestra/testbench": "^8.0",
"pestphp/pest": "^1.21",
"pestphp/pest-plugin-laravel": "^1.1",
"phpunit/phpunit": "^9.5|^10.0"
Expand Down Expand Up @@ -63,4 +61,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
}
}
10 changes: 10 additions & 0 deletions config/dusk-fakes.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<?php

return [
'bus' => [
'enabled' => env('DUSK_FAKE_BUS', false),
'storage_root' => storage_path('framework/testing/bus'),
],

'mails' => [
'enabled' => env('DUSK_FAKE_MAILS', false),
'storage_root' => storage_path('framework/testing/mails'),
Expand All @@ -10,4 +15,9 @@
'enabled' => env('DUSK_FAKE_NOTIFICATIONS', false),
'storage_root' => storage_path('framework/testing/notifications'),
],

'queue' => [
'enabled' => env('DUSK_FAKE_QUEUE', false),
'storage_root' => storage_path('framework/testing/queue'),
],
];
20 changes: 20 additions & 0 deletions src/Bus/PersistentBus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace ProtoneMedia\LaravelDuskFakes\Bus;

use Illuminate\Support\Facades\Bus;

trait PersistentBus
{
public function setUpPersistentBus()
{
Bus::swap(
app(UncachedPersistentBusFake::class)
);
}

public function tearDownPersistentBus()
{
Bus::cleanStorage();
}
}
129 changes: 129 additions & 0 deletions src/Bus/PersistentBusFake.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace ProtoneMedia\LaravelDuskFakes\Bus;

use Illuminate\Bus\PendingBatch;
use Illuminate\Contracts\Bus\QueueingDispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Illuminate\Support\Testing\Fakes\BusFake;
use Illuminate\Support\Testing\Fakes\PendingBatchFake;

class PersistentBusFake extends BusFake
{
private string $directory;

private string $storage;

public function __construct(QueueingDispatcher $dispatcher, $jobsToFake = [])
{
parent::__construct($dispatcher, $jobsToFake);

$this->directory = rtrim(config('dusk-fakes.bus.storage_root'), '/');

$this->storage = $this->directory.'/serialized';

(new Filesystem)->ensureDirectoryExists($this->directory);

$this->loadBus();
}

public function jobsToFake($jobsToFake = [])
{
$this->jobsToFake = Arr::wrap($jobsToFake);

$this->storeBus();
}

public function cleanStorage()
{
(new Filesystem)->cleanDirectory($this->directory);
}

public function loadBus(): self
{
$unserialized = file_exists($this->storage)
? rescue(fn () => unserialize(file_get_contents($this->storage)), [], false)
: [];

$this->jobsToFake = $unserialized['jobsToFake'] ?? [];
$this->commands = $unserialized['commands'] ?? [];
$this->commandsSync = $unserialized['commandsSync'] ?? [];
$this->commandsAfterResponse = $unserialized['commandsAfterResponse'] ?? [];
$this->batches = $unserialized['batches'] ?? [];

return $this;
}

public function dispatch($command)
{
return tap(parent::dispatch($command), fn () => $this->storeBus());
}

public function dispatchSync($command, $handler = null)
{
return tap(parent::dispatchSync($command, $handler), fn () => $this->storeBus());
}

public function dispatchNow($command, $handler = null)
{
return tap(parent::dispatchNow($command, $handler), fn () => $this->storeBus());
}

public function dispatchToQueue($command)
{
return tap(parent::dispatchToQueue($command), fn () => $this->storeBus());
}

public function dispatchAfterResponse($command)
{
return tap(parent::dispatchAfterResponse($command), fn () => $this->storeBus());
}

public function recordPendingBatch(PendingBatch $pendingBatch)
{
return tap(parent::recordPendingBatch($pendingBatch), fn () => $this->storeBus());
}

public function cleanupCommand(array $jobs): array
{
return collect($jobs)->map(function ($job) {
tap(invade($job), function ($job) {
if (! $job->job) {
return;
}

$job = invade($job->job);
$job->container = null;

if (! $job->instance) {
return;
}

invade($job->instance)->container = null;
invade($job->instance)->dispatcher = null;
});

return $job;
})->all();
}

private function storeBus()
{
(new Filesystem)->ensureDirectoryExists($this->directory);

file_put_contents($this->storage, serialize([
'jobsToFake' => $this->jobsToFake,
'commands' => collect($this->commands)->map([$this, 'cleanupCommand'])->all(),
'commandsSync' => collect($this->commandsSync)->map([$this, 'cleanupCommand'])->all(),
'commandsAfterResponse' => collect($this->commandsAfterResponse)->map([$this, 'cleanupCommand'])->all(),
'batches' => collect($this->batches)->each(function (PendingBatchFake $batch) {
tap(invade($batch), function ($batch) {
$batch->bus = null;
});

return $batch;
})->all(),
]));
}
}
41 changes: 41 additions & 0 deletions src/Bus/UncachedPersistentBusFake.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace ProtoneMedia\LaravelDuskFakes\Bus;

use Illuminate\Support\Traits\ForwardsCalls;

/**
* @mixin \Illuminate\Support\Testing\Fakes\BusFake
*/
class UncachedPersistentBusFake
{
use ForwardsCalls;

public function __construct(private PersistentBusFake $fake)
{
}

/**
* Handle dynamic method calls into the fake.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->forwardCallTo($this->fake->loadBus(), $method, $parameters);
}

/**
* Handle dynamic static method calls into the fake.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
return app(static::class)->$method(...$parameters);
}
}
Loading

0 comments on commit a963567

Please sign in to comment.