From 3f03f8e5685dd81ba191842e1b34dcb6adc9f1b6 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Sat, 1 Jun 2024 05:47:37 +0200 Subject: [PATCH] feat(testing): accept type-hinted requests as `assertSent` parameters --- src/Http/Faking/MockClient.php | 38 +++++++++++++++++++++++++++++++++- tests/Unit/MockClientTest.php | 35 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/Http/Faking/MockClient.php b/src/Http/Faking/MockClient.php index fb4d0838..730a47d1 100644 --- a/src/Http/Faking/MockClient.php +++ b/src/Http/Faking/MockClient.php @@ -416,7 +416,22 @@ private function checkClosureAgainstResponses(callable $closure): bool return false; } - // Let's first check if the latest response resolves the callable + // Let's first check if the callable type-hints the latest request class. + // If so, we try to find the corresponding request in the recorded responses + // and call the callable accordingly. We will only fail if it returns `false`. + + if ($fqcn = $this->getRequestClass($closure)) { + /** @var Response */ + foreach ($this->getRecordedResponses() as $response) { + if (get_class($request = $response->getPendingRequest()->getRequest()) !== $fqcn) { + continue; + } + + return $closure($request, $response) !== false; + } + } + + // Let's then check if the latest response resolves the callable // with a successful result. $lastResponse = $this->getLastResponse(); @@ -482,4 +497,25 @@ private function getRequestSentCount(): array return array_count_values($requests); } + + /** + * Get the FQCN of the request class if type-hinted. + * + * @return class-string + */ + private function getRequestClass(callable $closure): ?string + { + $reflection = new \ReflectionFunction($closure); + $parameters = $reflection->getParameters(); + + if (! ($fqcn = $parameters[0]->getType()?->getName())) { + return null; + } + + if (! is_a($fqcn, Request::class, allow_string: true)) { + return null; + } + + return $fqcn; + } } diff --git a/tests/Unit/MockClientTest.php b/tests/Unit/MockClientTest.php index f542bee0..11a88d28 100644 --- a/tests/Unit/MockClientTest.php +++ b/tests/Unit/MockClientTest.php @@ -2,10 +2,12 @@ declare(strict_types=1); +use Pest\Expectation; use Saloon\Http\Faking\MockClient; use Saloon\Http\Faking\MockResponse; use Saloon\Tests\Fixtures\Requests\UserRequest; use Saloon\Tests\Fixtures\Requests\ErrorRequest; +use PHPUnit\Framework\ExpectationFailedException; use Saloon\Exceptions\NoMockResponseFoundException; use Saloon\Tests\Fixtures\Connectors\TestConnector; use Saloon\Tests\Fixtures\Exceptions\TestResponseException; @@ -261,3 +263,36 @@ $response = connector()->send(new UserRequest, $mockClient); $response->throw(); }); + +test('`assertSent` accepts the request class as a type-hint', function () { + $mockClient = new MockClient([ + MockResponse::make(['name' => 'Sam']), + ]); + + $request = new UserRequest; + $request->headers()->add('X-Foo', 'bar'); + + connector()->send($request, $mockClient); + + $mockClient->assertSent(function (UserRequest $request) { + expect($request->headers()->all())->toMatchArray([ + 'X-Foo' => 'bar', + ]); + }); +}); + +test('`assertSent` fails or succeeds depending on the closure result when the closure is type-hinted', function (mixed $returns, bool $shouldThrow) { + $mockClient = new MockClient([ + MockResponse::make(['name' => 'Sam']), + ]); + + connector()->send(new UserRequest, $mockClient); + + expect(fn () => $mockClient->assertSent(fn (UserRequest $request) => $returns)) + ->when($shouldThrow, fn (Expectation $e) => $e->toThrow(ExpectationFailedException::class)) + ->when(! $shouldThrow, fn (Expectation $e) => $e->not->toThrow(ExpectationFailedException::class)); +})->with([ + [false, true], + [true, false], + [null, false], +]);