From 45d0f4986559d03f5b1bfcb12c7b98d97982b4a3 Mon Sep 17 00:00:00 2001 From: A G Date: Wed, 20 Nov 2024 19:51:48 -0500 Subject: [PATCH 1/2] Improvements to fake assertions: - Add support for optionally passing closures to methods that perform value assertions - Add count assertions - Enhancements to FakeWindowManagerTest::class --- src/Fakes/WindowManagerFake.php | 76 ++++++++++++++++-- tests/Fakes/FakeWindowManagerTest.php | 108 +++++++++++++++++++++++++- 2 files changed, 175 insertions(+), 9 deletions(-) diff --git a/src/Fakes/WindowManagerFake.php b/src/Fakes/WindowManagerFake.php index c3cf791..bd77a2a 100644 --- a/src/Fakes/WindowManagerFake.php +++ b/src/Fakes/WindowManagerFake.php @@ -2,6 +2,7 @@ namespace Native\Laravel\Fakes; +use Closure; use Illuminate\Support\Arr; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Windows\Window; @@ -71,19 +72,82 @@ public function get(string $id): Window return Arr::first($matchingWindows); } - public function assertOpened(string $id): void + /** + * @param string|Closure(string): bool $id + */ + public function assertOpened(string|Closure $id): void + { + if (is_callable($id) === false) { + PHPUnit::assertContains($id, $this->opened); + + return; + } + + $hit = empty( + array_filter( + $this->opened, + fn (string $openedId) => $id($openedId) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $id + */ + public function assertClosed(string|Closure $id): void + { + if (is_callable($id) === false) { + PHPUnit::assertContains($id, $this->closed); + + return; + } + + $hit = empty( + array_filter( + $this->closed, + fn (mixed $closedId) => $id($closedId) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $id + */ + public function assertHidden(string|Closure $id): void + { + if (is_callable($id) === false) { + PHPUnit::assertContains($id, $this->hidden); + + return; + } + + $hit = empty( + array_filter( + $this->hidden, + fn (mixed $hiddenId) => $id($hiddenId) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + public function assertOpenedCount(int $expected): void { - PHPUnit::assertContains($id, $this->opened); + PHPUnit::assertCount($expected, $this->opened); } - public function assertClosed(?string $id): void + public function assertClosedCount(int $expected): void { - PHPUnit::assertContains($id, $this->closed); + PHPUnit::assertCount($expected, $this->closed); } - public function assertHidden(?string $id): void + public function assertHiddenCount(int $expected): void { - PHPUnit::assertContains($id, $this->hidden); + PHPUnit::assertCount($expected, $this->hidden); } private function ensureForceReturnWindowsProvided(): void diff --git a/tests/Fakes/FakeWindowManagerTest.php b/tests/Fakes/FakeWindowManagerTest.php index 5f0fbb1..b95a79b 100644 --- a/tests/Fakes/FakeWindowManagerTest.php +++ b/tests/Fakes/FakeWindowManagerTest.php @@ -26,8 +26,24 @@ try { $fake->assertOpened('tertiary'); } catch (AssertionFailedError) { - expect(true)->toBeTrue(); + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was opened using callable', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->open('main'); + app(WindowManagerContract::class)->open('secondary'); + $fake->assertOpened(fn (string $id) => $id === 'main'); + $fake->assertOpened(fn (string $id) => $id === 'secondary'); + + try { + $fake->assertOpened(fn (string $id) => $id === 'tertiary'); + } catch (AssertionFailedError) { return; } @@ -46,8 +62,24 @@ try { $fake->assertClosed('tertiary'); } catch (AssertionFailedError) { - expect(true)->toBeTrue(); + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was closed using callable', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + app(WindowManagerContract::class)->close('main'); + app(WindowManagerContract::class)->close('secondary'); + + $fake->assertClosed(fn (string $id) => $id === 'main'); + $fake->assertClosed(fn (string $id) => $id === 'secondary'); + + try { + $fake->assertClosed(fn (string $id) => $id === 'tertiary'); + } catch (AssertionFailedError) { return; } @@ -66,8 +98,78 @@ try { $fake->assertHidden('tertiary'); } catch (AssertionFailedError) { - expect(true)->toBeTrue(); + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts that a window was hidden using callable', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->hide('main'); + app(WindowManagerContract::class)->hide('secondary'); + + $fake->assertHidden(fn (string $id) => $id === 'main'); + $fake->assertHidden(fn (string $id) => $id === 'secondary'); + + try { + $fake->assertHidden(fn (string $id) => $id === 'tertiary'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts opened count', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->open('main'); + app(WindowManagerContract::class)->open(); + app(WindowManagerContract::class)->open(); + + $fake->assertOpenedCount(3); + + try { + $fake->assertOpenedCount(4); + } catch (AssertionFailedError) { + return; + } + $this->fail('Expected assertion to fail'); +}); + +it('asserts closed count', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->close('main'); + app(WindowManagerContract::class)->close(); + app(WindowManagerContract::class)->close(); + + $fake->assertClosedCount(3); + + try { + $fake->assertClosedCount(4); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts hidden count', function () { + swap(WindowManagerContract::class, $fake = new WindowManagerFake); + + app(WindowManagerContract::class)->hide('main'); + app(WindowManagerContract::class)->hide(); + app(WindowManagerContract::class)->hide(); + + $fake->assertHiddenCount(3); + + try { + $fake->assertHiddenCount(4); + } catch (AssertionFailedError) { return; } From 44f685db85f168af9ef9e0d445ce7b543e039475 Mon Sep 17 00:00:00 2001 From: A G Date: Thu, 21 Nov 2024 00:08:27 -0500 Subject: [PATCH 2/2] Improvements to window faking: - Resolve test environment error caused FakeWindowManager::open() not returning a Window/PendingOpenWindow object. This now mimics the real WindowManager::open() behavior - Remove usage of PHPUnit assertions in FakeWindowManager::class for internal assertions. Now only used for final assertions. This change fixes issue where PHPUnit was reporting 2 assertions per ::assertX() call on the test double --- src/Facades/Window.php | 2 +- src/Fakes/WindowManagerFake.php | 27 +++++- tests/Fakes/FakeWindowManagerTest.php | 113 +++++++++++++++++++++----- 3 files changed, 118 insertions(+), 24 deletions(-) diff --git a/src/Facades/Window.php b/src/Facades/Window.php index e774755..f73e216 100644 --- a/src/Facades/Window.php +++ b/src/Facades/Window.php @@ -22,7 +22,7 @@ class Window extends Facade { public static function fake() { - return tap(new WindowManagerFake, function ($fake) { + return tap(static::getFacadeApplication()->make(WindowManagerFake::class), function ($fake) { static::swap($fake); }); } diff --git a/src/Fakes/WindowManagerFake.php b/src/Fakes/WindowManagerFake.php index bd77a2a..8604224 100644 --- a/src/Fakes/WindowManagerFake.php +++ b/src/Fakes/WindowManagerFake.php @@ -4,9 +4,11 @@ use Closure; use Illuminate\Support\Arr; +use Native\Laravel\Client\Client; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Windows\Window; use PHPUnit\Framework\Assert as PHPUnit; +use Webmozart\Assert\Assert; class WindowManagerFake implements WindowManagerContract { @@ -18,6 +20,10 @@ class WindowManagerFake implements WindowManagerContract public array $forcedWindowReturnValues = []; + public function __construct( + protected Client $client + ) {} + /** * @param array $windows */ @@ -31,6 +37,21 @@ public function alwaysReturnWindows(array $windows): self public function open(string $id = 'main') { $this->opened[] = $id; + + $this->ensureForceReturnWindowsProvided(); + + $matchingWindows = array_filter( + $this->forcedWindowReturnValues, + fn (Window $window) => $window->getId() === $id + ); + + if (empty($matchingWindows)) { + return $this->forcedWindowReturnValues[array_rand($this->forcedWindowReturnValues)]->setClient($this->client); + } + + Assert::count($matchingWindows, 1); + + return Arr::first($matchingWindows)->setClient($this->client); } public function close($id = null) @@ -66,8 +87,8 @@ public function get(string $id): Window $matchingWindows = array_filter($this->forcedWindowReturnValues, fn (Window $window) => $window->getId() === $id); - PHPUnit::assertNotEmpty($matchingWindows); - PHPUnit::assertCount(1, $matchingWindows); + Assert::notEmpty($matchingWindows); + Assert::count($matchingWindows, 1); return Arr::first($matchingWindows); } @@ -152,6 +173,6 @@ public function assertHiddenCount(int $expected): void private function ensureForceReturnWindowsProvided(): void { - PHPUnit::assertNotEmpty($this->forcedWindowReturnValues); + Assert::notEmpty($this->forcedWindowReturnValues, 'No windows were provided to return'); } } diff --git a/tests/Fakes/FakeWindowManagerTest.php b/tests/Fakes/FakeWindowManagerTest.php index b95a79b..b2f4af2 100644 --- a/tests/Fakes/FakeWindowManagerTest.php +++ b/tests/Fakes/FakeWindowManagerTest.php @@ -1,10 +1,13 @@ Http::response(status: 200)]); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows([ + new PendingOpenWindow('doesnt-matter'), + ]); app(WindowManagerContract::class)->open('main'); app(WindowManagerContract::class)->open('secondary'); @@ -33,7 +41,13 @@ }); it('asserts that a window was opened using callable', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + Http::fake(['*' => Http::response(status: 200)]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows([ + new PendingOpenWindow('doesnt-matter'), + ]); app(WindowManagerContract::class)->open('main'); app(WindowManagerContract::class)->open('secondary'); @@ -51,7 +65,7 @@ }); it('asserts that a window was closed', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->close('main'); app(WindowManagerContract::class)->close('secondary'); @@ -69,7 +83,7 @@ }); it('asserts that a window was closed using callable', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->close('main'); app(WindowManagerContract::class)->close('secondary'); @@ -87,7 +101,7 @@ }); it('asserts that a window was hidden', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->hide('main'); app(WindowManagerContract::class)->hide('secondary'); @@ -105,7 +119,7 @@ }); it('asserts that a window was hidden using callable', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->hide('main'); app(WindowManagerContract::class)->hide('secondary'); @@ -123,7 +137,13 @@ }); it('asserts opened count', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + Http::fake(['*' => Http::response(status: 200)]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows([ + new PendingOpenWindow('doesnt-matter'), + ]); app(WindowManagerContract::class)->open('main'); app(WindowManagerContract::class)->open(); @@ -141,7 +161,7 @@ }); it('asserts closed count', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->close('main'); app(WindowManagerContract::class)->close(); @@ -159,7 +179,7 @@ }); it('asserts hidden count', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->hide('main'); app(WindowManagerContract::class)->hide(); @@ -177,7 +197,7 @@ }); it('forces the return value of current window', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -188,7 +208,7 @@ }); it('forces the return value of all windows', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -199,7 +219,7 @@ }); it('forces the return value of a specific window', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -211,7 +231,7 @@ }); test('that the get method throws an exception if multiple matching window ids exist', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), @@ -219,26 +239,79 @@ ]); app(WindowManagerContract::class)->get('testA'); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); test('that the get method throws an exception if no matching window id exists', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); $fake->alwaysReturnWindows($windows = [ new WindowClass('testA'), ]); app(WindowManagerContract::class)->get('testB'); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); test('that the current method throws an exception if no forced window return values are provided', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->current(); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); test('that the all method throws an exception if no forced window return values are provided', function () { - swap(WindowManagerContract::class, $fake = new WindowManagerFake); + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); app(WindowManagerContract::class)->all(); -})->throws(AssertionFailedError::class); +})->throws(InvalidArgumentException::class); + +test('that the open method throws an exception if no forced window return values are provided', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + app(WindowManagerContract::class)->open('test'); +})->throws(InvalidArgumentException::class); + +test('that the open method throws an exception if multiple matching window ids exist', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows($windows = [ + new WindowClass('testA'), + new WindowClass('testA'), + ]); + + app(WindowManagerContract::class)->open('testA'); +})->throws(InvalidArgumentException::class); + +test('that the open method returns a random window if none match the id provided', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows($windows = [ + new PendingOpenWindow('testA'), + ]); + + expect($windows)->toContain(app(WindowManagerContract::class)->open('testC')); +}); + +test('that the open method returns a window if a matching window id exists', function () { + Http::fake([ + '*' => Http::response(status: 200), + ]); + + swap(WindowManagerContract::class, $fake = app(WindowManagerFake::class)); + + $fake->alwaysReturnWindows($windows = [ + new PendingOpenWindow('testA'), + ]); + + expect(app(WindowManagerContract::class)->open('testA'))->toBe($windows[0]); +});