Skip to content

Commit d81733b

Browse files
authored
Merge pull request #31 from Sammyjo20/feature/mocking-assertions
Added new mock assertions
2 parents 69e2bff + cfbaa1d commit d81733b

File tree

10 files changed

+649
-20
lines changed

10 files changed

+649
-20
lines changed

src/Clients/BaseMockClient.php

Lines changed: 251 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,56 @@
33
namespace Sammyjo20\Saloon\Clients;
44

55
use ReflectionClass;
6-
use Illuminate\Support\Str;
6+
use Sammyjo20\Saloon\Helpers\URLHelper;
77
use Sammyjo20\Saloon\Http\MockResponse;
8+
use PHPUnit\Framework\Assert as PHPUnit;
89
use Sammyjo20\Saloon\Http\SaloonRequest;
10+
use Sammyjo20\Saloon\Http\SaloonResponse;
911
use Sammyjo20\Saloon\Http\SaloonConnector;
12+
use Sammyjo20\Saloon\Helpers\ReflectionHelper;
1013
use Sammyjo20\Saloon\Exceptions\SaloonNoMockResponseFoundException;
11-
use Sammyjo20\Saloon\Exceptions\SaloonNoMockResponsesProvidedException;
1214
use Sammyjo20\Saloon\Exceptions\SaloonInvalidMockResponseCaptureMethodException;
1315

1416
class BaseMockClient
1517
{
18+
/**
19+
* Collection of all the responses that will be sequenced.
20+
*
21+
* @var array
22+
*/
1623
protected array $sequenceResponses = [];
1724

25+
/**
26+
* Collection of responses used only when a connector is called.
27+
*
28+
* @var array
29+
*/
1830
protected array $connectorResponses = [];
1931

32+
/**
33+
* Collection of responses used only when a request is called.
34+
*
35+
* @var array
36+
*/
2037
protected array $requestResponses = [];
2138

39+
/**
40+
* Collection of responses that will run when the request is matched.
41+
*
42+
* @var array
43+
*/
2244
protected array $urlResponses = [];
2345

2446
/**
25-
* @param array $responses
26-
* @throws SaloonNoMockResponsesProvidedException
47+
* Collection of all the recorded responses.
48+
*
49+
* @var array
50+
*/
51+
protected array $recordedResponses = [];
52+
53+
/**
54+
* @param array $mockData
55+
* @throws SaloonInvalidMockResponseCaptureMethodException
2756
*/
2857
public function __construct(array $mockData = [])
2958
{
@@ -48,6 +77,14 @@ public function addResponses(array $responses): void
4877
}
4978
}
5079

80+
/**
81+
* Add a mock response to the client
82+
*
83+
* @param MockResponse $response
84+
* @param string|null $captureMethod
85+
* @return void
86+
* @throws SaloonInvalidMockResponseCaptureMethodException
87+
*/
5188
public function addResponse(MockResponse $response, ?string $captureMethod = null): void
5289
{
5390
if (is_null($captureMethod)) {
@@ -84,6 +121,11 @@ public function addResponse(MockResponse $response, ?string $captureMethod = nul
84121
$this->urlResponses[$captureMethod] = $response;
85122
}
86123

124+
/**
125+
* Get the next response in the sequence
126+
*
127+
* @return mixed
128+
*/
87129
public function getNextFromSequence(): mixed
88130
{
89131
return array_shift($this->sequenceResponses);
@@ -134,7 +176,7 @@ public function guessNextResponse(SaloonRequest $request): MockResponse
134176
private function guessResponseFromUrl(SaloonRequest $request): ?MockResponse
135177
{
136178
foreach ($this->urlResponses as $url => $response) {
137-
if (! Str::is(Str::start($url, '*'), $request->getFullRequestUrl())) {
179+
if (! URLHelper::matches($url, $request->getFullRequestUrl())) {
138180
continue;
139181
}
140182

@@ -153,4 +195,208 @@ public function isEmpty(): bool
153195
{
154196
return empty($this->sequenceResponses) && empty($this->connectorResponses) && empty($this->requestResponses) && empty($this->urlResponses);
155197
}
198+
199+
/**
200+
* Record a response.
201+
*
202+
* @param SaloonResponse $response
203+
* @return void
204+
*/
205+
public function recordResponse(SaloonResponse $response): void
206+
{
207+
$this->recordedResponses[] = $response;
208+
}
209+
210+
/**
211+
* Get all the recorded responses
212+
*
213+
* @return array
214+
*/
215+
public function getRecordedResponses(): array
216+
{
217+
return $this->recordedResponses;
218+
}
219+
220+
/**
221+
* Get the last request that the mock manager sent.
222+
*
223+
* @return SaloonRequest|null
224+
*/
225+
public function getLastRequest(): ?SaloonRequest
226+
{
227+
return $this->getLastResponse()?->getOriginalRequest();
228+
}
229+
230+
/**
231+
* Get the last response that the mock manager sent.
232+
*
233+
* @return SaloonResponse|null
234+
*/
235+
public function getLastResponse(): ?SaloonResponse
236+
{
237+
if (empty($this->recordedResponses)) {
238+
return null;
239+
}
240+
241+
$lastResponse = end($this->recordedResponses);
242+
243+
reset($this->recordedResponses);
244+
245+
return $lastResponse;
246+
}
247+
248+
/**
249+
* Assert that a given request was sent.
250+
*
251+
* @param string|callable $value
252+
* @return void
253+
* @throws \ReflectionException
254+
*/
255+
public function assertSent(string|callable $value): void
256+
{
257+
$result = $this->checkRequestWasSent($value);
258+
259+
PHPUnit::assertTrue($result, 'An expected request was not sent.');
260+
}
261+
262+
/**
263+
* Assert that a given request was not sent.
264+
*
265+
* @param string|callable $request
266+
* @return void
267+
* @throws \ReflectionException
268+
*/
269+
public function assertNotSent(string|callable $request): void
270+
{
271+
$result = $this->checkRequestWasNotSent($request);
272+
273+
PHPUnit::assertTrue($result, 'An unexpected request was sent.');
274+
}
275+
276+
/**
277+
* Assert JSON data was sent
278+
*
279+
* @param string $request
280+
* @param array $data
281+
* @return void
282+
* @throws \ReflectionException
283+
*/
284+
public function assertSentJson(string $request, array $data): void
285+
{
286+
$this->assertSent($request);
287+
288+
$response = $this->findResponseByRequest($request);
289+
290+
PHPUnit::assertEquals($response->json(), $data, 'Expected request data was not sent.');
291+
}
292+
293+
/**
294+
* Assert that nothing was sent.
295+
*
296+
* @return void
297+
*/
298+
public function assertNothingSent(): void
299+
{
300+
PHPUnit::assertEmpty($this->getRecordedResponses(), 'Requests were sent.');
301+
}
302+
303+
/**
304+
* Assert a request count has been met.
305+
*
306+
* @param int $count
307+
* @return void
308+
*/
309+
public function assertSentCount(int $count): void
310+
{
311+
PHPUnit::assertCount($count, $this->getRecordedResponses());
312+
}
313+
314+
/**
315+
* Check if a given request was sent
316+
*
317+
* @param string|callable $request
318+
* @return bool
319+
* @throws \ReflectionException
320+
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException
321+
*/
322+
protected function checkRequestWasSent(string|callable $request): bool
323+
{
324+
$result = false;
325+
326+
if (is_callable($request)) {
327+
$result = $request($this->getLastRequest(), $this->getLastResponse());
328+
}
329+
330+
if (is_string($request)) {
331+
if (class_exists($request) && ReflectionHelper::isSubclassOf($request, SaloonRequest::class)) {
332+
$result = $this->findResponseByRequest($request) instanceof SaloonResponse;
333+
} else {
334+
$result = $this->findResponseByRequestUrl($request) instanceof SaloonResponse;
335+
}
336+
}
337+
338+
return $result;
339+
}
340+
341+
/**
342+
* Check if a request has not been sent.
343+
*
344+
* @param string|callable $request
345+
* @return bool
346+
* @throws \ReflectionException
347+
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException
348+
*/
349+
protected function checkRequestWasNotSent(string|callable $request): bool
350+
{
351+
return ! $this->checkRequestWasSent($request);
352+
}
353+
354+
/**
355+
* Assert a given request was sent.
356+
*
357+
* @param string $request
358+
* @return SaloonResponse|null
359+
*/
360+
public function findResponseByRequest(string $request): ?SaloonResponse
361+
{
362+
$lastRequest = $this->getLastRequest();
363+
364+
if ($lastRequest instanceof $request) {
365+
return $this->getLastResponse();
366+
}
367+
368+
foreach ($this->getRecordedResponses() as $recordedResponse) {
369+
if ($recordedResponse->getOriginalRequest() instanceof $request) {
370+
return $recordedResponse;
371+
}
372+
}
373+
374+
return null;
375+
}
376+
377+
/**
378+
* Find a request that matches a given url pattern
379+
*
380+
* @param string $url
381+
* @return SaloonResponse|null
382+
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException
383+
*/
384+
public function findResponseByRequestUrl(string $url): ?SaloonResponse
385+
{
386+
$lastRequest = $this->getLastRequest();
387+
388+
if ($lastRequest instanceof SaloonRequest && URLHelper::matches($url, $lastRequest->getFullRequestUrl())) {
389+
return $this->getLastResponse();
390+
}
391+
392+
foreach ($this->getRecordedResponses() as $recordedResponse) {
393+
$request = $recordedResponse->getOriginalRequest();
394+
395+
if (URLHelper::matches($url, $request->getFullRequestUrl())) {
396+
return $recordedResponse;
397+
}
398+
}
399+
400+
return null;
401+
}
156402
}

src/Helpers/URLHelper.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Helpers;
4+
5+
use Illuminate\Support\Str;
6+
7+
class URLHelper
8+
{
9+
/**
10+
* Check if a URL matches a given pattern
11+
*
12+
* @param string $pattern
13+
* @param string $value
14+
* @return bool
15+
*/
16+
public static function matches(string $pattern, string $value): bool
17+
{
18+
return Str::is(Str::start($pattern, '*'), $value);
19+
}
20+
}

src/Managers/RequestManager.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,13 @@ private function createResponse(array $requestOptions, Response $response, Reque
201201
/** @var SaloonResponse $response */
202202
$response = new $responseClass($requestOptions, $request, $response, $exception);
203203

204-
$response->setMocked($this->isMocking());
204+
// If we are mocking, we should record the request and response on the mock manager,
205+
// so we can run assertions on the responses.
206+
207+
if ($this->isMocking()) {
208+
$response->setMocked(true);
209+
$this->mockClient->recordResponse($response);
210+
}
205211

206212
if (property_exists($this->connector, 'shouldGuessStatusFromBody') || property_exists($this->request, 'shouldGuessStatusFromBody')) {
207213
$response->guessesStatusFromBody();

src/Traits/CollectsConfig.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,11 @@ public function addConfig(string $item, $value): self
7777

7878
/**
7979
* Get all headers or filter with a key.
80-
* Todo: Throw an error if it doesn't exist.
8180
*
8281
* @param string|null $key
8382
* @return array
8483
*/
85-
public function getConfig(string $key = null): array
84+
public function getConfig(string $key = null): mixed
8685
{
8786
if ($this->includeDefaultConfig === true) {
8887
$configBag = array_merge($this->defaultConfig(), $this->customConfig);

src/Traits/CollectsData.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,6 @@ public function getData(string $key = null): mixed
108108
return $dataBag;
109109
}
110110

111-
/**
112-
* Get an individual data
113-
*
114-
* @param string $key
115-
* @return string
116-
*/
117-
public function getDataByKey(string $key): string
118-
{
119-
return $this->getData($key);
120-
}
121-
122111
/**
123112
* Should we ignore the default data when calling `->getData()`?
124113
*

src/Traits/CollectsHeaders.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ public function addHeader(string $header, $value): self
7676

7777
/**
7878
* Get all headers or filter with a key.
79-
* Todo: Throw an error if it doesn't exist.
8079
*
8180
* @param string|null $key
8281
* @return array

0 commit comments

Comments
 (0)