Skip to content

Commit e3a4f1e

Browse files
authored
Merge pull request #90 from Sammyjo20/feature/recording-mock-client
Feature | Mocking Fixtures
2 parents c9a0930 + 6867fd0 commit e3a4f1e

22 files changed

+1006
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ composer.lock
33
vendor
44
.php-cs-fixer.cache
55
.phpunit.result.cache
6+
tests/Fixtures/Saloon

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
},
2727
"require-dev": {
2828
"friendsofphp/php-cs-fixer": "^3.5",
29+
"league/flysystem": "^3.0",
2930
"pestphp/pest": "^1.21",
3031
"spatie/ray": "^1.33"
3132
},

src/Clients/BaseMockClient.php

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Sammyjo20\Saloon\Clients;
44

55
use ReflectionClass;
6+
use Sammyjo20\Saloon\Http\Fixture;
67
use Sammyjo20\Saloon\Helpers\URLHelper;
78
use Sammyjo20\Saloon\Http\MockResponse;
89
use PHPUnit\Framework\Assert as PHPUnit;
@@ -80,12 +81,12 @@ public function addResponses(array $responses): void
8081
/**
8182
* Add a mock response to the client
8283
*
83-
* @param MockResponse|callable $response
84+
* @param MockResponse|Fixture|callable $response
8485
* @param string|null $captureMethod
8586
* @return void
8687
* @throws SaloonInvalidMockResponseCaptureMethodException
8788
*/
88-
public function addResponse(MockResponse|callable $response, ?string $captureMethod = null): void
89+
public function addResponse(MockResponse|Fixture|callable $response, ?string $captureMethod = null): void
8990
{
9091
if (is_null($captureMethod)) {
9192
$this->sequenceResponses[] = $response;
@@ -135,11 +136,12 @@ public function getNextFromSequence(): mixed
135136
* Guess the next response based on the request.
136137
*
137138
* @param SaloonRequest $request
138-
* @return MockResponse
139+
* @return MockResponse|Fixture
139140
* @throws SaloonNoMockResponseFoundException
141+
* @throws \ReflectionException
140142
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException
141143
*/
142-
public function guessNextResponse(SaloonRequest $request): MockResponse
144+
public function guessNextResponse(SaloonRequest $request): MockResponse|Fixture
143145
{
144146
$requestClass = get_class($request);
145147

@@ -173,7 +175,7 @@ public function guessNextResponse(SaloonRequest $request): MockResponse
173175
* @return MockResponse|null
174176
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException
175177
*/
176-
private function guessResponseFromUrl(SaloonRequest $request): MockResponse|callable|null
178+
private function guessResponseFromUrl(SaloonRequest $request): MockResponse|Fixture|callable|null
177179
{
178180
foreach ($this->urlResponses as $url => $response) {
179181
if (! URLHelper::matches($url, $request->getFullRequestUrl())) {
@@ -458,18 +460,22 @@ private function checkHistoryEmpty(): bool
458460
}
459461

460462
/**
461-
* Create the mock response. If it is a callable, we will call it.
463+
* Get the mock value.
462464
*
463-
* @param MockResponse|callable $mockResponse
465+
* @param MockResponse|Fixture|callable $mockable
464466
* @param SaloonRequest $request
465-
* @return MockResponse
467+
* @return MockResponse|Fixture
466468
*/
467-
private function mockResponseValue(MockResponse|callable $mockResponse, SaloonRequest $request): MockResponse
469+
private function mockResponseValue(MockResponse|Fixture|callable $mockable, SaloonRequest $request): MockResponse|Fixture
468470
{
469-
if ($mockResponse instanceof MockResponse) {
470-
return $mockResponse;
471+
if ($mockable instanceof MockResponse) {
472+
return $mockable;
473+
}
474+
475+
if ($mockable instanceof Fixture) {
476+
return $mockable;
471477
}
472478

473-
return $mockResponse($request);
479+
return $mockable($request);
474480
}
475481
}

src/Data/FixtureData.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Data;
4+
5+
use JsonSerializable;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Sammyjo20\Saloon\Http\MockResponse;
8+
9+
class FixtureData implements JsonSerializable
10+
{
11+
/**
12+
* Constructor
13+
*
14+
* @param int $statusCode
15+
* @param array $headers
16+
* @param mixed $data
17+
*/
18+
public function __construct(
19+
public int $statusCode,
20+
public array $headers = [],
21+
public mixed $data = null,
22+
) {
23+
//
24+
}
25+
26+
/**
27+
* Create an instance from file contents
28+
*
29+
* @param string $contents
30+
* @return static
31+
* @throws \JsonException
32+
*/
33+
public static function fromFile(string $contents): static
34+
{
35+
$fileData = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
36+
37+
return new static(
38+
statusCode: $fileData['statusCode'],
39+
headers: $fileData['headers'],
40+
data: $fileData['data']
41+
);
42+
}
43+
44+
/**
45+
* Create an instance from a Guzzle response
46+
*
47+
* @param ResponseInterface $response
48+
* @return static
49+
*/
50+
public static function fromGuzzleResponse(ResponseInterface $response): static
51+
{
52+
return new static(
53+
statusCode: $response->getStatusCode(),
54+
headers: $response->getHeaders(),
55+
data: (string)$response->getBody(),
56+
);
57+
}
58+
59+
/**
60+
* Encode the instance to be stored as a file
61+
*
62+
* @return string
63+
* @throws \JsonException
64+
*/
65+
public function toFile(): string
66+
{
67+
return json_encode($this, JSON_THROW_ON_ERROR);
68+
}
69+
70+
/**
71+
* Create a mock response from the fixture
72+
*
73+
* @return MockResponse
74+
*/
75+
public function toMockResponse(): MockResponse
76+
{
77+
return new MockResponse($this->data, $this->statusCode, $this->headers);
78+
}
79+
80+
/**
81+
* Define the JSON object if this class is converted into JSON
82+
*
83+
* @return array
84+
*/
85+
public function jsonSerialize(): array
86+
{
87+
return [
88+
'statusCode' => $this->statusCode,
89+
'headers' => $this->headers,
90+
'data' => $this->data,
91+
];
92+
}
93+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Exceptions;
4+
5+
class DirectoryNotFoundException extends SaloonException
6+
{
7+
/**
8+
* Constructor
9+
*
10+
* @param string $directory
11+
*/
12+
public function __construct(string $directory)
13+
{
14+
parent::__construct(sprintf('The directory "%s" does not exist or is not a valid directory.', $directory));
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Exceptions;
4+
5+
class FixtureMissingException extends SaloonException
6+
{
7+
/**
8+
* Constructor
9+
*
10+
* @param string $name
11+
*/
12+
public function __construct(string $name)
13+
{
14+
parent::__construct(sprintf('The fixture "%s" could not be found in storage.', $name));
15+
}
16+
}

src/Exceptions/OAuthConfigValidationException.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
namespace Sammyjo20\Saloon\Exceptions;
44

5-
use \Exception;
6-
7-
class OAuthConfigValidationException extends Exception
5+
class OAuthConfigValidationException extends SaloonException
86
{
97
//
108
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Exceptions;
4+
5+
class UnableToCreateDirectoryException extends SaloonException
6+
{
7+
/**
8+
* Constructor
9+
*
10+
* @param string $directory
11+
*/
12+
public function __construct(string $directory)
13+
{
14+
parent::__construct(sprintf('Unable to create the directory: %s.', $directory));
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Exceptions;
4+
5+
class UnableToCreateFileException extends SaloonException
6+
{
7+
/**
8+
* Constructor
9+
*
10+
* @param string $path
11+
*/
12+
public function __construct(string $path)
13+
{
14+
parent::__construct(sprintf('We were unable to create the "%s" file.', $path));
15+
}
16+
}

src/Helpers/MockConfig.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Helpers;
4+
5+
class MockConfig
6+
{
7+
/**
8+
* Default fixture path
9+
*
10+
* @var string
11+
*/
12+
private static string $fixturePath = 'tests/Fixtures/Saloon';
13+
14+
/**
15+
* Denotes if an exception should be thrown if a fixture is missing.
16+
*
17+
* @var bool
18+
*/
19+
private static bool $throwOnMissingFixtures = false;
20+
21+
/**
22+
* Set the fixture path
23+
*
24+
* @param string $path
25+
* @return void
26+
*/
27+
public static function setFixturePath(string $path): void
28+
{
29+
self::$fixturePath = $path;
30+
}
31+
32+
/**
33+
* Throw an exception if a fixture doesn't exist instead of recording it.
34+
*
35+
* @return void
36+
*/
37+
public static function throwOnMissingFixtures(): void
38+
{
39+
self::$throwOnMissingFixtures = true;
40+
}
41+
42+
/**
43+
* Return the fixture path
44+
*
45+
* @return string
46+
*/
47+
public static function getFixturePath(): string
48+
{
49+
return self::$fixturePath;
50+
}
51+
52+
/**
53+
* Should we throw an exception if a fixture is missing?
54+
*
55+
* @return bool
56+
*/
57+
public static function isThrowingOnMissingFixtures(): bool
58+
{
59+
return self::$throwOnMissingFixtures;
60+
}
61+
}

0 commit comments

Comments
 (0)