Skip to content

Commit

Permalink
Merge pull request #425 from innocenzi/feat/merge-fixture
Browse files Browse the repository at this point in the history
Feature | Allow merging data in mocked fixture responses
  • Loading branch information
Sammyjo20 authored Feb 2, 2025
2 parents 8d721de + e3b6941 commit de1e0be
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 1 deletion.
39 changes: 39 additions & 0 deletions src/Helpers/ArrayHelpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,43 @@ public static function get(array $array, string|int|null $key, mixed $default =

return $array;
}

/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* @param array $array
* @param string|int|null $key
* @return array
*/
public static function set(&$array, $key, $value)

Check failure on line 88 in src/Helpers/ArrayHelpers.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Saloon\Helpers\ArrayHelpers::set() has parameter $array with no value type specified in iterable type array.

Check failure on line 88 in src/Helpers/ArrayHelpers.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Saloon\Helpers\ArrayHelpers::set() has parameter $value with no type specified.

Check failure on line 88 in src/Helpers/ArrayHelpers.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Saloon\Helpers\ArrayHelpers::set() return type has no value type specified in iterable type array.
{
if (is_null($key)) {
return $array = $value;
}

$keys = explode('.', $key);

Check failure on line 94 in src/Helpers/ArrayHelpers.php

View workflow job for this annotation

GitHub Actions / phpstan

Parameter #2 $string of function explode expects string, int|string given.

foreach ($keys as $i => $key) {
if (count($keys) === 1) {
break;
}

unset($keys[$i]);

// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if (! isset($array[$key]) || ! is_array($array[$key])) {
$array[$key] = [];
}

$array = &$array[$key];
}

$array[array_shift($keys)] = $value;

return $array;
}
}
68 changes: 67 additions & 1 deletion src/Http/Faking/Fixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

use Saloon\MockConfig;
use Saloon\Helpers\Storage;
use Saloon\Helpers\ArrayHelpers;
use Saloon\Data\RecordedResponse;
use Saloon\Helpers\FixtureHelper;
use Saloon\Exceptions\FixtureException;
use Saloon\Exceptions\FixtureMissingException;
use Saloon\Repositories\Body\StringBodyRepository;

class Fixture
{
Expand All @@ -28,6 +30,16 @@ class Fixture
*/
protected Storage $storage;

/**
* Data to merge in the mocked response.
*/
protected ?array $merge = null;

Check failure on line 36 in src/Http/Faking/Fixture.php

View workflow job for this annotation

GitHub Actions / phpstan

Property Saloon\Http\Faking\Fixture::$merge type has no value type specified in iterable type array.

/**
* Closure to modify the returned data with.
*/
protected ?\Closure $through = null;

/**
* Constructor
*/
Expand All @@ -37,6 +49,26 @@ public function __construct(string $name = '', ?Storage $storage = null)
$this->storage = $storage ?? new Storage(MockConfig::getFixturePath(), true);
}

/**
* Specify data to merge with the mock response data.
*/
public function merge(array $merge = []): static

Check failure on line 55 in src/Http/Faking/Fixture.php

View workflow job for this annotation

GitHub Actions / phpstan

Method Saloon\Http\Faking\Fixture::merge() has parameter $merge with no value type specified in iterable type array.
{
$this->merge = $merge;

return $this;
}

/**
* Specify a closure to modify the mock response data with.
*/
public function through(\Closure $through): static
{
$this->through = $through;

return $this;
}

/**
* Attempt to get the mock response from the fixture.
*/
Expand All @@ -46,7 +78,41 @@ public function getMockResponse(): ?MockResponse
$fixturePath = $this->getFixturePath();

if ($storage->exists($fixturePath)) {
return RecordedResponse::fromFile($storage->get($fixturePath))->toMockResponse();
$response = RecordedResponse::fromFile($storage->get($fixturePath))->toMockResponse();

if (is_null($this->merge) && is_null($this->through)) {
return $response;
}

// First, we get the body as an array. If we're dealing with
// a `StringBodyRepository`, we have to encode it first.
if (! is_array($body = $response->body()->all())) {
$body = json_decode($body ?: '[]', associative: true, flags: \JSON_THROW_ON_ERROR);
}

// We can then merge the data in the body usingthrough
// the ArrayHelpers for dot-notation support.
if (is_array($this->merge)) {
foreach ($this->merge as $key => $value) {
ArrayHelpers::set($body, $key, $value);
}
}

// If specified, we pass the body through a function that
// may modify the mock response data.
if (! is_null($this->through)) {
$body = call_user_func($this->through, $body);
}

// We then set the mutated data back in the repository. If we're dealing
// with a `StringBodyRepository`, we need to encode it back to string.
$response->body()->set(
$response->body() instanceof StringBodyRepository
? json_encode($body)
: $body
);

return $response;
}

if (MockConfig::isThrowingOnMissingFixtures() === true) {
Expand Down
1 change: 1 addition & 0 deletions tests/Fixtures/Saloon/users.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"statusCode":200,"headers":{"Date":"Mon, 11 Sep 2023 21:20:43 GMT","Content-Type":"application\/json","Content-Length":"63","Connection":"keep-alive","access-control-allow-origin":"*","Cache-Control":"no-cache, private","x-ratelimit-limit":"1000","x-ratelimit-remaining":"999","x-frame-options":"SAMEORIGIN","x-xss-protection":"1; mode=block","x-content-type-options":"nosniff","CF-Cache-Status":"DYNAMIC","Report-To":"{\"endpoints\":[{\"url\":\"https:\\\/\\\/a.nel.cloudflare.com\\\/report\\\/v3?s=6FQ2ADSCfKyvoEWPs9KjRyZXMfPJmR6bUu%2BXwFY0wYhRdQacLvV%2FTlZdmMX8vS%2FkoEoaTr%2B0kDpLjTd8PFH0%2FhuFShA7T1FxFLE9b6kf%2BM8T4FIJPiaWJSq2MnsZzle07j%2BR\"}],\"group\":\"cf-nel\",\"max_age\":604800}","NEL":"{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}","Server":"cloudflare","CF-RAY":"8052f4d0a80f0743-MAN","alt-svc":"h3=\":443\"; ma=86400"},"data":"{\"data\":[{\"name\":\"Jon\",\"actual_name\":\"Jon Doe\",\"twitter\":\"@jondoe\"},{\"name\":\"Jane\",\"actual_name\":\"Jane Doe\",\"twitter\":\"@janedoe\"}]}"}
53 changes: 53 additions & 0 deletions tests/Unit/FixtureDataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

namespace Saloon\Tests\Unit;

use Pest\Expectation;
use Saloon\Data\RecordedResponse;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;
use Saloon\Tests\Fixtures\Requests\DTORequest;

test('you can create a fixture data object from a file string', function () {
$data = [
Expand Down Expand Up @@ -60,3 +63,53 @@
expect($serialized)->toEqual(json_encode($data, JSON_PRETTY_PRINT));
expect($fixtureData->toFile())->toEqual($serialized);
});

test('arbitrary data can be merged in the fixture', function () {
$response = connector()->send(new DTORequest, new MockClient([
MockResponse::fixture('user')->merge([
'name' => 'Sam Carré',
]),
]));

expect($response->dto())
->name->toBe('Sam Carré')
->actualName->toBe('Sam')
->twitter->toBe('@carre_sam');
});

test('arbitrary data using dot-notation can be merged in the fixture', function () {
$response = connector()->send(new DTORequest, new MockClient([
MockResponse::fixture('users')->merge([
'data.0.twitter' => '@jon_doe',
]),
]));

expect($response->json('data'))
->toHaveCount(2)
->sequence(
fn (Expectation $e) => $e->twitter->toBe('@jon_doe'),
fn (Expectation $e) => $e->twitter->toBe('@janedoe'),
);
});

test('a closure can be used to modify the mock response data', function () {
$response = connector()->send(new DTORequest, new MockClient([
MockResponse::fixture('users')->through(fn (array $data) => array_merge_recursive($data, [
'data' => [
[
'name' => 'Sam',
'actual_name' => 'Carré',
'twitter' => '@carre_sam',
],
],
])),
]));

expect($response->json('data'))
->toHaveCount(3)
->sequence(
fn (Expectation $e) => $e->twitter->toBe('@jondoe'),
fn (Expectation $e) => $e->twitter->toBe('@janedoe'),
fn (Expectation $e) => $e->twitter->toBe('@carre_sam'),
);
});

0 comments on commit de1e0be

Please sign in to comment.