├── config └── proxy.php ├── src ├── UrlGenerator.php ├── Providers │ └── ProxyServiceProvider.php ├── Exceptions │ └── ProxyException.php ├── Proxy.php ├── Http.php ├── Responses │ └── ProxyResponse.php └── Requests │ └── PendingRequest.php ├── composer.json └── README.md /config/proxy.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'Accept' => 'application/json' 10 | ], 11 | 12 | 'base_url' => env('PROXY_BASE_URL', env('APP_URL')), 13 | ]; 14 | -------------------------------------------------------------------------------- /src/UrlGenerator.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom( 13 | __DIR__.'/../../config/proxy.php', 14 | 'proxy' 15 | ); 16 | } 17 | 18 | public function boot() 19 | { 20 | if ($this->app->runningInConsole()) { 21 | $this->publishes( 22 | [__DIR__.'/../../config/proxy.php' => config_path('proxy.php')], 23 | 'config' 24 | ); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Exceptions/ProxyException.php: -------------------------------------------------------------------------------- 1 | proxyResponse = $proxyResponse; 17 | 18 | parent::__construct( 19 | $this->prepareMessage($proxyResponse->response()), 20 | $proxyResponse->response()->status() 21 | ); 22 | } 23 | 24 | protected function prepareMessage(Response $response): string 25 | { 26 | $proxyPath = optional($this->proxyResponse->response()->effectiveUri())->getPath(); 27 | 28 | return "Request from $proxyPath failed with status code {$response->status()}"; 29 | } 30 | 31 | public function render(): JsonResponse 32 | { 33 | $jsonResponse = $this->proxyResponse->json(); 34 | 35 | if (isset($jsonResponse['error']['message'])) { 36 | $errorMessage = $jsonResponse['error']['message']; 37 | $errors = $jsonResponse['error']['errors'] ?? null; 38 | } elseif (isset($jsonResponse['trace'])) { 39 | $errorMessage = $this->getMessage(); 40 | $errors = $jsonResponse; 41 | } else { 42 | $errorMessage = $jsonResponse['message'] ?? $jsonResponse['error'] ?? $this->getMessage(); 43 | $errors = null; 44 | } 45 | 46 | return apiResponse()->errors($errorMessage, $errors)->status($this->getCode())->get(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "behamin/service-proxy", 3 | "description": "for proxy or sending requests to other services with useful utilities", 4 | "keywords": [ 5 | "behamin", 6 | "request", 7 | "http", 8 | "laravel", 9 | "guzzle", 10 | "service", 11 | "proxy" 12 | ], 13 | "type": "library", 14 | "require": { 15 | "php": "^7.4 || ^8.3", 16 | "ext-json": "*", 17 | "laravel/framework": "^7.0 || ^8.0 || ^9.0 || ^10.0", 18 | "guzzlehttp/guzzle": "^7.0", 19 | "behamin/bresources": "^1.5 || ^2.2" 20 | }, 21 | "scripts": { 22 | "test": "phpunit --color=always" 23 | }, 24 | "require-dev": { 25 | "orchestra/testbench": "^v6.0 || ^v7.0" 26 | }, 27 | "license": "MIT", 28 | "authors": [ 29 | { 30 | "name": "Alireza Bahram", 31 | "email": "alib327@gmail.com" 32 | }, 33 | { 34 | "name": "Omid Alizadeh", 35 | "email": "om.alizadeh1@gmail.com" 36 | }, 37 | { 38 | "name": "Hebrahimzadeh", 39 | "email": "abi.hossein@gmail.com" 40 | }, 41 | { 42 | "name": "Mohammad Hosein Abedini", 43 | "email": "mohammadhoseinabedini@gmail.com" 44 | } 45 | ], 46 | "minimum-stability": "dev", 47 | "prefer-stable": true, 48 | "autoload": { 49 | "psr-4": { 50 | "Behamin\\ServiceProxy\\": "src/" 51 | } 52 | }, 53 | "autoload-dev": { 54 | "psr-4": { 55 | "Behamin\\ServiceProxy\\Tests\\": "tests/" 56 | } 57 | }, 58 | "extra": { 59 | "laravel": { 60 | "providers": [ 61 | "Behamin\\ServiceProxy\\Providers\\ProxyServiceProvider" 62 | ], 63 | "aliases": { 64 | "Proxy": "Behamin\\ServiceProxy\\Proxy" 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Proxy.php: -------------------------------------------------------------------------------- 1 | mockPath = $jsonPath; 79 | return $this; 80 | } 81 | 82 | foreach ($jsonPath as $url => $path) { 83 | $fakeItem = [$this->trimUrl($url) => $this->getJsonContent($path)]; 84 | $this->fakes[key($fakeItem)] = Arr::first($fakeItem); 85 | HttpFactory::fake($fakeItem); 86 | } 87 | 88 | $this->mockPath = null; 89 | 90 | return $this; 91 | } 92 | 93 | public function clearExistingFakes(): self 94 | { 95 | $reflection = new ReflectionObject(HttpFactory::getFacadeRoot()); 96 | $property = $reflection->getProperty('stubCallbacks'); 97 | $property->setAccessible(true); 98 | $property->setValue(HttpFactory::getFacadeRoot(), collect()); 99 | 100 | return $this; 101 | } 102 | 103 | public function trimUrl($url): string 104 | { 105 | return trim($url, '/'); 106 | } 107 | 108 | private function getJsonContent($jsonPath) 109 | { 110 | $mockDirectory = base_path().DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'mock'.DIRECTORY_SEPARATOR; 111 | $jsonFile = file_get_contents($mockDirectory.$jsonPath); 112 | return json_decode($jsonFile, true, 512, JSON_THROW_ON_ERROR); 113 | } 114 | 115 | public function hasFakes() 116 | { 117 | return !empty($this->fakes); 118 | } 119 | 120 | public function hasFake($url) 121 | { 122 | return !empty($this->fakes[$this->trimUrl($url)]); 123 | } 124 | 125 | /** 126 | * @return string|null 127 | */ 128 | public function getMockPath(): ?string 129 | { 130 | return $this->mockPath; 131 | } 132 | 133 | public function isSetMocking(): bool 134 | { 135 | return !empty($this->mockPath); 136 | } 137 | 138 | /** 139 | * Execute a method against a new pending request instance. 140 | * 141 | * @param string $method 142 | * @param array $parameters 143 | * @return mixed 144 | * @throws \JsonException 145 | */ 146 | public function __call($method, $parameters) 147 | { 148 | if (static::hasMacro($method)) { 149 | return $this->macroCall($method, $parameters); 150 | } 151 | 152 | return tap($this->newPendingRequest(), function ($request) { 153 | $request->stub($this->stubCallbacks); 154 | })->{$method}(...$parameters); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Responses/ProxyResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 23 | } 24 | 25 | /** 26 | * Get data from response or a subset of it. 27 | * 28 | * @param array|string|null $keys 29 | * @param mixed $default 30 | * @return array|mixed 31 | */ 32 | public function data($keys = null, $default = null) 33 | { 34 | $data = $this->json()['data']; 35 | 36 | if (is_null($keys)) { 37 | return $data; 38 | } 39 | 40 | if (is_string($keys)) { 41 | return $data[$keys] ?? $default; 42 | } 43 | 44 | return Arr::only($data, $keys); 45 | } 46 | 47 | /** 48 | * Get response message. 49 | * 50 | * @return string|null 51 | */ 52 | public function message(): ?string 53 | { 54 | return $this->json()['message']; 55 | } 56 | 57 | public function error() 58 | { 59 | return $this->json()['error']; 60 | } 61 | 62 | public function items() 63 | { 64 | return $this->json()['data']['items']; 65 | } 66 | 67 | public function count() 68 | { 69 | return $this->json()['data']['count']; 70 | } 71 | 72 | /** 73 | * Get response data as a collection. 74 | * 75 | * @return Collection 76 | */ 77 | public function collect(): Collection 78 | { 79 | if (isset($this->json()['data']['items'])) { 80 | return Collection::make($this->items()); 81 | } 82 | 83 | return Collection::make($this->data()); 84 | } 85 | 86 | public function json() 87 | { 88 | return $this->response()->json(); 89 | } 90 | 91 | /** 92 | * Get response status code. 93 | * 94 | * @return int 95 | */ 96 | public function status(): int 97 | { 98 | return $this->response()->status(); 99 | } 100 | 101 | public function jsonResponse(): JsonResponse 102 | { 103 | return response()->json($this->response()->json(), $this->status()); 104 | } 105 | 106 | public function toJson($options = 0) 107 | { 108 | return json_encode($this->json(), $options | JSON_THROW_ON_ERROR); 109 | } 110 | 111 | public function toArray() 112 | { 113 | return $this->json(); 114 | } 115 | 116 | public function onSuccess(Closure $closure): ProxyResponse 117 | { 118 | if ($this->response()->successful()) { 119 | $closure($this); 120 | } 121 | 122 | return $this; 123 | } 124 | 125 | public function onDataSuccess(Closure $closure): ProxyResponse 126 | { 127 | if ($this->response()->successful()) { 128 | $closure($this->data()); 129 | } 130 | 131 | return $this; 132 | } 133 | 134 | public function onCollectionSuccess(Closure $closure): ProxyResponse 135 | { 136 | if ($this->response()->successful()) { 137 | $closure($this->items(), $this->count()); 138 | } 139 | 140 | return $this; 141 | } 142 | 143 | public function onError(Closure $closure): ProxyResponse 144 | { 145 | if ($this->response()->failed()) { 146 | $closure($this->toException()); 147 | } 148 | 149 | return $this; 150 | } 151 | 152 | /** 153 | * Create an exception if a server or client error occurred. 154 | * 155 | * @return ProxyException|void 156 | */ 157 | public function toException(): ProxyException 158 | { 159 | if ($this->response()->failed()) { 160 | return new ProxyException($this); 161 | } 162 | } 163 | 164 | /** 165 | * Throw an exception if a server or client error occurred. 166 | * 167 | * @return $this 168 | */ 169 | public function throw(?Closure $closure = null): ProxyResponse 170 | { 171 | if ($this->response()->failed()) { 172 | throw tap($this->toException(), function ($exception) use ($closure) { 173 | if (!is_null($closure)) { 174 | $closure($this, $exception); 175 | } 176 | }); 177 | } 178 | 179 | return $this; 180 | } 181 | 182 | public function toResponse($request) 183 | { 184 | if ($this->response()->header('Content-Type') === 'application/json') { 185 | return $this->jsonResponse(); 186 | } 187 | 188 | return response($this->response()->body(), $this->status()); 189 | } 190 | 191 | public function offsetExists($offset): bool 192 | { 193 | return $this->response()->offsetExists($offset); 194 | } 195 | 196 | public function offsetGet($offset) 197 | { 198 | return $this->response()->offsetGet($offset); 199 | } 200 | 201 | public function offsetSet($offset, $value): void 202 | { 203 | $this->response()->offsetSet($offset, $value); 204 | } 205 | 206 | public function offsetUnset($offset): void 207 | { 208 | $this->response()->offsetUnset($offset); 209 | } 210 | 211 | public function response(): HttpResponse 212 | { 213 | return $this->response; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/Requests/PendingRequest.php: -------------------------------------------------------------------------------- 1 | domain = $domain; 34 | 35 | return $this; 36 | } 37 | 38 | public function request(Request $request, string $service): ProxyResponse 39 | { 40 | $this->service = $service; 41 | $path = $request->path(); 42 | $data = $request->all(); 43 | 44 | foreach ($request->allFiles() as $name => $file) { 45 | unset($data[$name]); 46 | $this->attach($name, $request->file($name)->getContent(), $request->file($name)->getClientOriginalName()); 47 | } 48 | 49 | switch ($request->method()) { 50 | case Request::METHOD_GET: 51 | return $this->get($path, $data); 52 | case Request::METHOD_POST: 53 | return $this->post($path, $data); 54 | case Request::METHOD_DELETE: 55 | return $this->delete($path, $data); 56 | case Request::METHOD_HEAD: 57 | return $this->head($path, $data); 58 | case Request::METHOD_PATCH: 59 | return $this->patch($path, $data); 60 | case Request::METHOD_PUT: 61 | return $this->put($path, $data); 62 | default: 63 | throw new NotAcceptableHttpException(); 64 | } 65 | } 66 | 67 | public function get(string $url = null, $query = null) 68 | { 69 | return $this->respond($url, $query, Request::METHOD_GET); 70 | } 71 | 72 | public function delete($url = null, $data = []) 73 | { 74 | return $this->respond($url, $data, Request::METHOD_DELETE); 75 | } 76 | 77 | public function head(string $url = null, $query = null) 78 | { 79 | return $this->respond($url, $query, Request::METHOD_HEAD); 80 | } 81 | 82 | public function patch($url, $data = []) 83 | { 84 | return $this->respond($url, $data, Request::METHOD_PATCH); 85 | } 86 | 87 | public function post(string $url, $data = []) 88 | { 89 | return $this->respond($url, $data, Request::METHOD_POST); 90 | } 91 | 92 | public function put($url, $data = []) 93 | { 94 | return $this->respond($url, $data, Request::METHOD_PUT); 95 | } 96 | 97 | public function prepare(): void 98 | { 99 | $this->withHeaders(config('proxy.global_headers', [])); 100 | } 101 | 102 | protected function makePromise(string $method, string $url, array $options = []): PromiseInterface 103 | { 104 | return $this->promise = $this->sendRequest($method, $url, $options) 105 | ->then(function (MessageInterface $message) { 106 | return new ProxyResponse(tap(new Response($message), function ($response) { 107 | $this->populateResponse($response); 108 | $this->dispatchResponseReceivedEvent($response); 109 | })); 110 | }) 111 | ->otherwise(function (TransferException $e) { 112 | return $e instanceof RequestException ? $this->populateResponse(new Response($e->getResponse())) : $e; 113 | }); 114 | } 115 | 116 | /** 117 | * @param $url 118 | * @return bool 119 | */ 120 | private function isValidUrl($url): bool 121 | { 122 | $pattern = '/^.+?\..+$/'; 123 | return preg_match($pattern, $url) != false; 124 | } 125 | 126 | /** 127 | * @param null|string $path 128 | * @return string 129 | */ 130 | private function fullUrl(?string $path): string 131 | { 132 | $baseUrl = UrlGenerator::baseUrl($this->domain); 133 | $servicePath = $this->service; 134 | if (Str::endsWith($baseUrl, '/')) { 135 | $baseUrl = Str::substr($baseUrl, 0, -1); 136 | } 137 | 138 | if (Str::startsWith($servicePath, '/')) { 139 | $servicePath = Str::substr($baseUrl, 1); 140 | } 141 | 142 | $finalPath = Str::startsWith($path, '/') ? Str::substr($path, 1) : $path; 143 | 144 | $prefix = $baseUrl . ($servicePath === '' ? $servicePath : '/' . $servicePath); 145 | 146 | return $this->isValidUrl($path) ? $finalPath : $prefix . '/' . $finalPath; 147 | } 148 | 149 | private function respond($url, $data, $method) 150 | { 151 | try { 152 | if ($this->factory->isSetMocking() && $this->isHttpRequestMethod($method)) { 153 | $this->factory->mock([$url => $this->factory->getMockPath()]); 154 | } 155 | 156 | if (app()->runningUnitTests() && $this->factory instanceof Http && $this->factory->hasFake($url)) { 157 | /** Http will remove / from start url */ 158 | $result = HttpFactory::$method($url); 159 | } else { 160 | $this->prepare(); 161 | $method = Str::lower($method); 162 | $result = parent::$method($this->fullUrl($url), $data); 163 | } 164 | 165 | if ($result instanceof PromiseInterface) { 166 | return $result; 167 | } 168 | return new ProxyResponse($result); 169 | } catch (\Exception $exception) { 170 | throw new InvalidArgumentException($exception->getMessage()); 171 | } 172 | } 173 | 174 | private function isHttpRequestMethod($method): bool 175 | { 176 | return in_array(Str::lower($method), ['post', 'get', 'head', 'delete', 'put', 'patch']); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Service Proxy 2 | 3 | Internal communication between services with useful tools 4 |
5 | Make request by laravel http client 6 | 7 | ## Installation 8 | 9 | ```bash 10 | composer require behamin/service-proxy 11 | ``` 12 | 13 | ### Publish config 14 | 15 | ```bash 16 | php artisan vendor:publish --provider="Behamin\ServiceProxy\Providers\ProxyServiceProvider" --tag config 17 | ``` 18 | 19 | ### Add services 20 | 21 | Add your project's base url and global headers in `proxy.php` config 22 | 23 | ```php 24 | return [ 25 | /** 26 | * Headers added to every request 27 | */ 28 | 'global_headers' => [ 29 | 'Accept' => 'application/json', 30 | ... 31 | ], 32 | 33 | 'base_url' => env('PROXY_BASE_URL', env('APP_URL')), 34 | ]; 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### Normal usage 40 | 41 | ```php 42 | use Behamin\ServiceProxy\Proxy; 43 | 44 | // Http Get 45 | Proxy::withToken('Your bearer token') 46 | ->acceptJson() 47 | ->retry(3) 48 | ->withHeaders([ 49 | "Content-Type" => "application\json" 50 | ])->get('api/articles'); 51 | 52 | Proxy::post('api/articles', [ 53 | "title" => "Test title", 54 | "body" => "Test body" 55 | ]); 56 | 57 | Proxy::patch('api/articles/1', [ 58 | "title" => "Test title", 59 | "body" => "Test body" 60 | ]); 61 | 62 | Proxy::put('api/articles', [ 63 | "title" => "Test title", 64 | "body" => "Test body" 65 | ]); 66 | 67 | Proxy::delete('api/articles/1'); 68 | ``` 69 | 70 | ### Using http request 71 | 72 | ```php 73 | 74 | use Behamin\ServiceProxy\Proxy; 75 | use Illuminate\Http\Request; 76 | 77 | public function index(Request $request) { 78 | $serviceName = 'test-service'; 79 | Proxy::request($request, $serviceName); 80 | } 81 | 82 | ``` 83 | 84 | ### Proxy events 85 | 86 | #### On success 87 | 88 | ```php 89 | use Behamin\ServiceProxy\Proxy; 90 | use Behamin\ServiceProxy\Responses\ProxyResponse; 91 | 92 | Proxy::get('api/articles/1')->onSuccess(function (ProxyResponse $proxyResponse) { 93 | $data = $proxyResponse->data(); 94 | $message = $proxyResponse->message(); 95 | $response = $proxyResponse->response(); 96 | $items = $proxyResponse->items(); 97 | $count = $proxyResponse->count(); 98 | ... 99 | }); 100 | ``` 101 | 102 | #### On error 103 | 104 | ```php 105 | use Behamin\ServiceProxy\Proxy; 106 | use Behamin\ServiceProxy\Exceptions\ProxyException; 107 | 108 | Proxy::get('api/articles/1')->onSuccess(function (ProxyException $proxyException) { 109 | $proxyResponse = $proxyException->proxyResponse; 110 | $trace = $proxyException->getTraceAsString(); 111 | ... 112 | }); 113 | ``` 114 | 115 | #### On data success 116 | ```php 117 | use Behamin\ServiceProxy\Proxy; 118 | 119 | Proxy::get('api/articles/1')->onDataSuccess(function (array $data) { 120 | $id = $data['id']; 121 | }); 122 | ``` 123 | 124 | #### On data collection success 125 | ```php 126 | use Behamin\ServiceProxy\Proxy; 127 | 128 | Proxy::get('api/articles/1')->onCollectionSuccess(function (array $items, int $count) { 129 | ... 130 | }); 131 | ``` 132 | 133 | 134 | ### Proxy response methods 135 | ```php 136 | use Behamin\ServiceProxy\Proxy; 137 | 138 | $proxyResponse = Proxy::get('api/articles/1'); 139 | ``` 140 | 141 | | Method | Description | 142 | | ----------------------------- | ---------------------------------------------- | 143 | | data() | given data | 144 | | items() | give items | 145 | | count() | given items count | 146 | | errors() | given errors if there is | 147 | | message() | given message | 148 | | onSuccess($closure) | When http request is successful | 149 | | onError($closure) | When http request is with error | 150 | | onCollectionSuccess($closure) | Get collection when http request is successful | 151 | | onDataSuccess($closure) | Get data when http request is successful | 152 | | throw() | Throw error if http request failed | 153 | | toException() | Get exception if http request failed | 154 | 155 | ### Proxy request methods 156 | 157 | | Method | Return Type | 158 | | ----------------------------- | ---------------------------------------------- | 159 | fake($callback = null) | \Illuminate\Http\Client\Factory 160 | accept(string $contentType) | \Behamin\ServiceProxy\Http 161 | acceptJson() | \Behamin\ServiceProxy\Http 162 | asForm() | \Behamin\ServiceProxy\Http 163 | asJson() | \Behamin\ServiceProxy\Http 164 | asMultipart() | \Behamin\ServiceProxy\Http 165 | async() | \Behamin\ServiceProxy\Http 166 | attach(string array $name, string $contents = '', string null $filename = null, array $headers = []) | \Behamin\ServiceProxy\Http 167 | baseUrl(string $url) | \Behamin\ServiceProxy\Http 168 | beforeSending(callable $callback) | \Behamin\ServiceProxy\Http 169 | bodyFormat(string $format) | \Behamin\ServiceProxy\Http 170 | contentType(string $contentType) | \Behamin\ServiceProxy\Http 171 | dd() | \Behamin\ServiceProxy\Http 172 | dump() | \Behamin\ServiceProxy\Http 173 | retry(int $times, int $sleep = 0) | \Behamin\ServiceProxy\Http 174 | sink(string|resource $to) | \Behamin\ServiceProxy\Http 175 | stub(callable $callback) | \Behamin\ServiceProxy\Http 176 | timeout(int $seconds) | \Behamin\ServiceProxy\Http 177 | withBasicAuth(string $username, string $password) | \Behamin\ServiceProxy\Http 178 | withBody(resource|string $content, string $contentType) | \Behamin\ServiceProxy\Http 179 | withCookies(array $cookies, string $domain) | \Behamin\ServiceProxy\Http 180 | withDigestAuth(string $username, string $password) | \Behamin\ServiceProxy\Http 181 | withHeaders(array $headers) | \Behamin\ServiceProxy\Http 182 | withMiddleware(callable $middleware) | \Behamin\ServiceProxy\Http 183 | withOptions(array $options) | \Behamin\ServiceProxy\Http 184 | withToken(string $token, string $type = 'Bearer') | \Behamin\ServiceProxy\Http 185 | withUserAgent(string $userAgent) | \Behamin\ServiceProxy\Http 186 | withoutRedirecting() | \Behamin\ServiceProxy\Http 187 | withoutVerifying() | \Behamin\ServiceProxy\Http 188 | pool(callable $callback) | array 189 | request(Request $request, string $service) | \Behamin\ServiceProxy\Responses\ProxyResponse 190 | get(string $url, array|string|null $query = null) | \Behamin\ServiceProxy\Responses\ProxyResponse 191 | delete(string $url, array $data = []) | \Behamin\ServiceProxy\Responses\ProxyResponse 192 | head(string $url, array|string|null $query = null) | \Behamin\ServiceProxy\Responses\ProxyResponse 193 | patch(string $url, array $data = []) | \Behamin\ServiceProxy\Responses\ProxyResponse 194 | post(string $url, array $data = []) | \Behamin\ServiceProxy\Responses\ProxyResponse 195 | put(string $url, array $data = []) | \Behamin\ServiceProxy\Responses\ProxyResponse 196 | send(string $method, string $url, array $options = []) | \Behamin\ServiceProxy\Responses\ProxyResponse 197 | fakeSequence(string $urlPattern = '*') | \Illuminate\Http\Client\ResponseSequence 198 | assertSent(callable $callback) | void 199 | assertNotSent(callable $callback) | void 200 | assertNothingSent() | void 201 | assertSentCount(int $count) | void 202 | assertSequencesAreEmpty() | void 203 | 204 | ### Mocking proxy response 205 | You can use `mock()` on Proxy class before calling http methods and pass the json path in your 'tests/mock' directory, to mock a json for faking your Proxy response in test mode. 206 | Example: 207 | 208 | ```php 209 | use Behamin\ServiceProxy\Proxy; 210 | Proxy::mock('response.json')->get('address'); 211 | ``` 212 | --------------------------------------------------------------------------------