From 2726ceb4f49db6e43b8414a40f3e6d6e2c456027 Mon Sep 17 00:00:00 2001 From: A G Date: Tue, 19 Nov 2024 18:50:10 -0500 Subject: [PATCH] Fake test double for WindowManager::Class: - Usage of (relatively lenient) WindowManagerContract::class in Laravel container - Implement WindowManagerFake::class with several testing assertions and helper methods - Laravel-style facade Window::fake() method - Tests --- src/Contracts/WindowManager.php | 23 +++++ src/Facades/Window.php | 11 +- src/Fakes/WindowManagerFake.php | 94 +++++++++++++++++ src/NativeServiceProvider.php | 7 ++ src/Windows/WindowManager.php | 3 +- tests/Fakes/FakeWindowManagerTest.php | 142 ++++++++++++++++++++++++++ 6 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 src/Contracts/WindowManager.php create mode 100644 src/Fakes/WindowManagerFake.php create mode 100644 tests/Fakes/FakeWindowManagerTest.php diff --git a/src/Contracts/WindowManager.php b/src/Contracts/WindowManager.php new file mode 100644 index 0000000..e4bbe63 --- /dev/null +++ b/src/Contracts/WindowManager.php @@ -0,0 +1,23 @@ + + */ + public function all(): array; + + public function get(string $id): Window; +} diff --git a/src/Facades/Window.php b/src/Facades/Window.php index 5ac1e90..e774755 100644 --- a/src/Facades/Window.php +++ b/src/Facades/Window.php @@ -3,6 +3,8 @@ namespace Native\Laravel\Facades; use Illuminate\Support\Facades\Facade; +use Native\Laravel\Contracts\WindowManager as WindowManagerContract; +use Native\Laravel\Fakes\WindowManagerFake; /** * @method static \Native\Laravel\Windows\PendingOpenWindow open(string $id = 'main') @@ -18,8 +20,15 @@ */ class Window extends Facade { + public static function fake() + { + return tap(new WindowManagerFake, function ($fake) { + static::swap($fake); + }); + } + protected static function getFacadeAccessor() { - return \Native\Laravel\Windows\WindowManager::class; + return WindowManagerContract::class; } } diff --git a/src/Fakes/WindowManagerFake.php b/src/Fakes/WindowManagerFake.php new file mode 100644 index 0000000..2f2a1ce --- /dev/null +++ b/src/Fakes/WindowManagerFake.php @@ -0,0 +1,94 @@ + $windows + */ + public function alwaysReturnWindows(array $windows): self + { + $this->forcedWindowReturnValues = $windows; + + return $this; + } + + public function open(string $id = 'main') + { + $this->opened[] = $id; + } + + public function close($id = null) + { + $this->closed[] = $id; + } + + public function hide($id = null) + { + $this->hidden[] = $id; + } + + public function current(): Window + { + $this->ensureForceReturnWindowsProvided(); + + return $this->forcedWindowReturnValues[array_rand($this->forcedWindowReturnValues)]; + } + + /** + * @return array + */ + public function all(): array + { + $this->ensureForceReturnWindowsProvided(); + + return $this->forcedWindowReturnValues; + } + + public function get(string $id): Window + { + $this->ensureForceReturnWindowsProvided(); + + $matchingWindows = array_filter($this->forcedWindowReturnValues, fn (Window $window) => $window->getId() === $id); + + PHPUnit::assertNotEmpty($matchingWindows); + PHPUnit::assertCount(1, $matchingWindows); + + return Arr::first($matchingWindows); + } + + public function assertOpened(string $id): void + { + PHPUnit::assertContains($id, $this->opened); + } + + public function assertClosed(?string $id): void + { + PHPUnit::assertContains($id, $this->closed); + } + + public function assertHidden(?string $id): void + { + PHPUnit::assertContains($id, $this->hidden); + } + + private function ensureForceReturnWindowsProvided(): void + { + PHPUnit::assertNotEmpty($this->forcedWindowReturnValues); + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index d9c28a1..dc2e258 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -3,6 +3,7 @@ namespace Native\Laravel; use Illuminate\Console\Application; +use Illuminate\Foundation\Application as Foundation; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; @@ -12,9 +13,11 @@ use Native\Laravel\Commands\MigrateCommand; use Native\Laravel\Commands\MinifyApplicationCommand; use Native\Laravel\Commands\SeedDatabaseCommand; +use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; use Native\Laravel\Logging\LogWatcher; +use Native\Laravel\Windows\WindowManager as WindowManagerImplementation; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -47,6 +50,10 @@ public function packageRegistered() return new MigrateCommand($app['migrator'], $app['events']); }); + $this->app->bind(WindowManagerContract::class, function (Foundation $app) { + return $app->make(WindowManagerImplementation::class); + }); + if (config('nativephp-internal.running')) { $this->app->singleton( \Illuminate\Contracts\Debug\ExceptionHandler::class, diff --git a/src/Windows/WindowManager.php b/src/Windows/WindowManager.php index 49574e6..6404686 100644 --- a/src/Windows/WindowManager.php +++ b/src/Windows/WindowManager.php @@ -4,8 +4,9 @@ use Native\Laravel\Client\Client; use Native\Laravel\Concerns\DetectsWindowId; +use Native\Laravel\Contracts\WindowManager as WindowManagerContract; -class WindowManager +class WindowManager implements WindowManagerContract { use DetectsWindowId; diff --git a/tests/Fakes/FakeWindowManagerTest.php b/tests/Fakes/FakeWindowManagerTest.php new file mode 100644 index 0000000..5f0fbb1 --- /dev/null +++ b/tests/Fakes/FakeWindowManagerTest.php @@ -0,0 +1,142 @@ +toBeInstanceOf(WindowManagerFake::class); +}); + +it('asserts that a window was opened', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->open('main'); + app(WindowManagerContract::class)->open('secondary'); + + $fake->assertOpened('main'); + $fake->assertOpened('secondary'); + + try { + $fake->assertOpened('tertiary'); + } catch (AssertionFailedError) { + expect(true)->toBeTrue(); + + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was closed', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->close('main'); + app(WindowManagerContract::class)->close('secondary'); + + $fake->assertClosed('main'); + $fake->assertClosed('secondary'); + + try { + $fake->assertClosed('tertiary'); + } catch (AssertionFailedError) { + expect(true)->toBeTrue(); + + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was hidden', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->hide('main'); + app(WindowManagerContract::class)->hide('secondary'); + + $fake->assertHidden('main'); + $fake->assertHidden('secondary'); + + try { + $fake->assertHidden('tertiary'); + } catch (AssertionFailedError) { + expect(true)->toBeTrue(); + + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('forces the return value of current window', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testB'), + ]); + + expect($windows)->toContain(app(WindowManagerContract::class)->current()); +}); + +it('forces the return value of all windows', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testB'), + ]); + + expect(app(WindowManagerContract::class)->all())->toBe($windows); +}); + +it('forces the return value of a specific window', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testB'), + ]); + + expect(app(WindowManagerContract::class)->get('testA'))->toBe($windows[0]); + expect(app(WindowManagerContract::class)->get('testB'))->toBe($windows[1]); +}); + +test('that the get method throws an exception if multiple matching window ids exist', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testA'), + ]); + + app(WindowManagerContract::class)->get('testA'); +})->throws(AssertionFailedError::class); + +test('that the get method throws an exception if no matching window id exists', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + ]); + + app(WindowManagerContract::class)->get('testB'); +})->throws(AssertionFailedError::class); + +test('that the current method throws an exception if no forced window return values are provided', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->current(); +})->throws(AssertionFailedError::class); + +test('that the all method throws an exception if no forced window return values are provided', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->all(); +})->throws(AssertionFailedError::class);