Skip to content

Commit 8d721de

Browse files
authored
Merge pull request #424 from innocenzi/feat/assert-sent-typehint
Feature | Accept type-hinted requests as `assertSent` parameters
2 parents d5bb5d6 + b4d847e commit 8d721de

File tree

3 files changed

+87
-1
lines changed

3 files changed

+87
-1
lines changed

phpstan.baseline.neon

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,18 @@ parameters:
4949
message: "#^Match arm is unreachable because previous comparison is always true.$#"
5050
count: 1
5151
path: src/Http/Pool.php
52+
53+
-
54+
message: "#^Method Saloon\\\\Http\\\\Faking\\\\MockClient\\:\\:getRequestClass\\(\\) should return class-string|null but returns class-string<Saloon\\\\Http\\\\Request>|Saloon\\\\Http\\\\Request$#"
55+
count: 1
56+
path: src/Http/Faking/MockClient.php
57+
58+
-
59+
message: "#^Call to an undefined method ReflectionType\\:\\:getName\\(\\)\\.$#"
60+
count: 1
61+
path: src/Http/Faking/MockClient.php
62+
63+
-
64+
message: "#^Parameter \\#1 \\$function of class ReflectionFunction constructor expects Closure|string, callable\\(\\)\\: mixed given\\.$#"
65+
count: 1
66+
path: src/Http/Faking/MockClient.php

src/Http/Faking/MockClient.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,22 @@ private function checkClosureAgainstResponses(callable $closure, ?int $index = n
458458
return $closure($request, $response);
459459
}
460460

461-
// Let's first check if the latest response resolves the callable
461+
// Let's first check if the callable type-hints the latest request class.
462+
// If so, we try to find the corresponding request in the recorded responses
463+
// and call the callable accordingly. We will only fail if it returns `false`.
464+
465+
if ($fqcn = $this->getRequestClass($closure)) {
466+
/** @var Response */
467+
foreach ($this->getRecordedResponses() as $response) {
468+
if (get_class($request = $response->getPendingRequest()->getRequest()) !== $fqcn) {
469+
continue;
470+
}
471+
472+
return $closure($request, $response) !== false;
473+
}
474+
}
475+
476+
// Let's then check if the latest response resolves the callable
462477
// with a successful result.
463478

464479
$lastResponse = $this->getLastResponse();
@@ -524,4 +539,25 @@ private function getRequestSentCount(): array
524539

525540
return array_count_values($requests);
526541
}
542+
543+
/**
544+
* Get the FQCN of the request class if type-hinted.
545+
*
546+
* @return class-string
547+
*/
548+
private function getRequestClass(callable $closure): ?string
549+
{
550+
$reflection = new \ReflectionFunction($closure);
551+
$parameters = $reflection->getParameters();
552+
553+
if (! ($fqcn = $parameters[0]->getType()?->getName())) {
554+
return null;
555+
}
556+
557+
if (! is_a($fqcn, Request::class, allow_string: true)) {
558+
return null;
559+
}
560+
561+
return $fqcn;
562+
}
527563
}

tests/Unit/MockClientTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
declare(strict_types=1);
44

5+
use Pest\Expectation;
56
use Saloon\Http\Faking\MockClient;
67
use Saloon\Http\Faking\MockResponse;
78
use Saloon\Tests\Fixtures\Requests\UserRequest;
89
use Saloon\Tests\Fixtures\Requests\ErrorRequest;
10+
use PHPUnit\Framework\ExpectationFailedException;
911
use Saloon\Exceptions\NoMockResponseFoundException;
1012
use Saloon\Tests\Fixtures\Connectors\TestConnector;
1113
use Saloon\Tests\Fixtures\Exceptions\TestResponseException;
@@ -261,3 +263,36 @@
261263
$response = connector()->send(new UserRequest, $mockClient);
262264
$response->throw();
263265
});
266+
267+
test('`assertSent` accepts the request class as a type-hint', function () {
268+
$mockClient = new MockClient([
269+
MockResponse::make(['name' => 'Sam']),
270+
]);
271+
272+
$request = new UserRequest;
273+
$request->headers()->add('X-Foo', 'bar');
274+
275+
connector()->send($request, $mockClient);
276+
277+
$mockClient->assertSent(function (UserRequest $request) {
278+
expect($request->headers()->all())->toMatchArray([
279+
'X-Foo' => 'bar',
280+
]);
281+
});
282+
});
283+
284+
test('`assertSent` fails or succeeds depending on the closure result when the closure is type-hinted', function (mixed $returns, bool $shouldThrow) {
285+
$mockClient = new MockClient([
286+
MockResponse::make(['name' => 'Sam']),
287+
]);
288+
289+
connector()->send(new UserRequest, $mockClient);
290+
291+
expect(fn () => $mockClient->assertSent(fn (UserRequest $request) => $returns))
292+
->when($shouldThrow, fn (Expectation $e) => $e->toThrow(ExpectationFailedException::class))
293+
->when(! $shouldThrow, fn (Expectation $e) => $e->not->toThrow(ExpectationFailedException::class));
294+
})->with([
295+
[false, true],
296+
[true, false],
297+
[null, false],
298+
]);

0 commit comments

Comments
 (0)