Skip to content

Commit

Permalink
feat(testing): accept type-hinted requests as assertSent parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
innocenzi committed Jun 1, 2024
1 parent 25cecb8 commit 3f03f8e
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 1 deletion.
38 changes: 37 additions & 1 deletion src/Http/Faking/MockClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
}
35 changes: 35 additions & 0 deletions tests/Unit/MockClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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],
]);

0 comments on commit 3f03f8e

Please sign in to comment.