PHPackages                             ciareis/bypass - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. ciareis/bypass

ActiveLibrary[HTTP &amp; Networking](/categories/http)

ciareis/bypass
==============

Bypass for PHP provides a quick way to create a custom instead of an actual HTTP server to return prebaked responses to client requests. This is most useful in tests, when you want to create a mock HTTP server and test how your HTTP client handles different types of responses from the server.

v3.0.0(3mo ago)11813.5k↓13.6%116MITPHPPHP ^8.2CI passing

Since May 19Pushed 3mo ago5 watchersCompare

[ Source](https://github.com/ciareis/bypass)[ Packagist](https://packagist.org/packages/ciareis/bypass)[ RSS](/packages/ciareis-bypass/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (5)Versions (39)Used By (6)

 [![Bypass Logo](docs/img/logo.png)](docs/img/logo.png)

Bypass for PHP
==============

[](#bypass-for-php)

 [About](#about) | [Requirements](#requirements) | [Installation](#installation) | [Writing Tests](#writing-tests) | [API Reference](#api-reference) | [Examples](#examples) | [Troubleshooting](#troubleshooting) | [Contributing](#contributing) | [Credits](#credits) | [Inspired](#inspired)

 [ ![US flag in base64](https://camo.githubusercontent.com/b54db9f64b98fc9ff563a49bae27383696825aef99f6e7685d584f95cd9d0854/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f456e676c69736820285553292d666c61672e7376673f636f6c6f723d353535353535267374796c653d666c6174266c6f676f3d646174613a696d6167652f7376672b786d6c3b6261736536342c50484e325a79423462577875637a30696148523063446f764c336433647935334d793576636d63764d6a41774d43397a646d636949485a705a58644362336739496a41674d4341784d6a4d31494459314d43496765473173626e4d3665477870626d7339496d6830644841364c79393364336375647a4d7562334a6e4c7a45354f546b7665477870626d73695067304b5047526c5a6e4d2b44516f385a7942705a44306964573570623234695067304b5048567a5a534235505349744c6a49784e69496765477870626d733661484a6c5a6a3069493367304969382b44516f3864584e6c49486873615735724f6d68795a57593949694e344e4349765067304b5048567a5a534235505349754d6a45324969423462476c75617a706f636d566d5053496a637a59694c7a344e436a77765a7a344e436a786e49476c6b50534a344e43492b44516f3864584e6c49486873615735724f6d68795a57593949694e7a4e6949765067304b5048567a5a534235505349754d4455304969423462476c75617a706f636d566d5053496a637a55694c7a344e436a783163325567655430694c6a45774f43496765477870626d733661484a6c5a6a306949334d324969382b44516f3864584e6c49486b39496934784e6a496949486873615735724f6d68795a57593949694e7a4e5349765067304b5043396e5067304b5047636761575139496e4d31496a344e436a783163325567654430694c5334794e54496949486873615735724f6d68795a57593949694e7a644746794969382b44516f3864584e6c49486739496930754d5449324969423462476c75617a706f636d566d5053496a63335268636949765067304b5048567a5a53423462476c75617a706f636d566d5053496a63335268636949765067304b5048567a5a534234505349754d5449324969423462476c75617a706f636d566d5053496a63335268636949765067304b5048567a5a534234505349754d6a55794969423462476c75617a706f636d566d5053496a63335268636949765067304b5043396e5067304b5047636761575139496e4d32496a344e436a783163325567654430694c5334774e6a4d6949486873615735724f6d68795a57593949694e7a4e5349765067304b5048567a5a534234505349754d7a45314969423462476c75617a706f636d566d5053496a63335268636949765067304b5043396e5067304b5047636761575139496e4e30595849695067304b5048567a5a53423462476c75617a706f636d566d5053496a63485169494852795957357a5a6d39796254306962574630636d6c344b4330754f4441354d4449674c5334314f4463334f5341754e5467334e7a6b674c5334344d446b774d694177494441704969382b44516f3864584e6c49486873615735724f6d68795a57593949694e776443496764484a68626e4e6d62334a7450534a74595852796158676f4c6a4d774f544179494330754f5455784d4459674c6a6b314d5441324943347a4d446b774d694177494441704969382b44516f3864584e6c49486873615735724f6d68795a57593949694e77644349765067304b5048567a5a53423462476c75617a706f636d566d5053496a63485169494852795957357a5a6d397962543069636d39305958526c4b4463794b5349765067304b5048567a5a53423462476c75617a706f636d566d5053496a63485169494852795957357a5a6d397962543069636d39305958526c4b4445304e436b694c7a344e436a77765a7a344e436a78775958526f49475a706247773949694e6d5a6d596949476c6b50534a77644349675a443069545330754d5459794e537777494441744c6a55674c6a45324d6a55734d486f69494852795957357a5a6d39796254306963324e686247556f4c6a41324d5459704969382b44516f38634746306143426d615778735053496a596d597759544d77496942705a443069633352796158426c4969426b50534a744d437777614445794d7a56324e54426f4c5445794d7a56364969382b44516f384c32526c5a6e4d2b44516f38634746306143426d615778735053496a5a6d5a6d4969426b50534a744d437777614445794d7a56324e6a5577614330784d6a4d31656949765067304b5048567a5a53423462476c75617a706f636d566d5053496a633352796158426c4969382b44516f3864584e6c49486b39496a45774d43496765477870626d733661484a6c5a6a306949334e30636d6c775a5349765067304b5048567a5a534235505349794d44416949486873615735724f6d68795a57593949694e7a64484a70634755694c7a344e436a783163325567655430694d7a41774969423462476c75617a706f636d566d5053496a633352796158426c4969382b44516f3864584e6c49486b39496a51774d43496765477870626d733661484a6c5a6a306949334e30636d6c775a5349765067304b5048567a5a534235505349314d44416949486873615735724f6d68795a57593949694e7a64484a70634755694c7a344e436a783163325567655430694e6a41774969423462476c75617a706f636d566d5053496a633352796158426c4969382b44516f38634746306143426d615778735053496a4d4441794f4459344969426b50534a744d437777614451354e48597a4e54426f4c5451354e486f694c7a344e436a78316332556765477870626d733661484a6c5a6a3069493356756157397549694230636d467563325a76636d3039496d316864484a70654367324e5441674d434177494459314d4341794e4463674d5463314b5349765067304b5043397a646d632b44516f3d) ](README.md) [ ![BR flag in base64](https://camo.githubusercontent.com/5f8253407654759832d064373ee940475c053c6109abf2dab9c5e09835f4c13d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f506f7274756775c3aa7320284252292d677261792e7376673f6c6f676f3d646174613a696d6167652f706e673b6261736536342c6956424f5277304b47676f414141414e5355684555674141414241414141414c4341494141414435674a7075414141414247644254554541414b2f494e77574b3651414141426c30525668305532396d64486468636d554151575276596d5567535731685a3256535a57466b6558484a5a5477414141486a5355524256486a61596d52495a6b43416677774d6632446b4c7a43434d79446f42774e41414c45416c5456474e2f356e59506a2f2f7838512f50332f392b2b2f767a5a61333167592f6d77357a2f546e3378386739382b6633372f2f2f666e39392f6571326c554141515453384a2f682f374e507a2f39433550373957526a383966392f7a762f2f667a744c765056657a507a727a2b382f66332f2f2b76744c686c384761414e41414945312f5038505641315536716e374e565471623158567041762f4a48372f2b612f383438586d7470426c6a3339504f38674d315050377a322b677177414369416e6f597043395446396e4233344e5666357a3458706f5a4a62456a4a4b66576145664c374b4c6c626155524b6a384f706a303852664956622b424e6741454542505157314c38502b62362f6d62362f2f732f772b2f2b6e633446302f395032636a3635786448632b702f515233392f2f392f4164484a39412f36306c3859766a494142424154304a594837356a53747637357a7743534d4259384258544d7858762f3231657a66486a3958352f334245534479354a664279372f5a75426e414167676b41312f2f76783539346b7061436e4c6c6f652f736d4c615654392f666633792f2b2f502f772b752f2b4a755737666877532f745361795058724f796372457966475141434341576f41312f2f6f4f434449676d373266753476793662344c442f392f532f332f2f2f73392b53323879792b392f4c4541662f2f6b4c436856674341454545456a44377a392f4a48676b51654877443867556a5637394f397236437a504c76366c72314f55467757483946786a63762f2f394263596f413041414d544934496d49524f5559524d66325841526b4142426741386b4d76516633712b323441414141415355564f524b35435949493d) ](docs/README_pt-BR.md)

 [ ![Tests](https://github.com/ciareis/bypass/actions/workflows/php.yml/badge.svg?branch=main) ](https://github.com/ciareis/bypass/actions/workflows/php.yml) [ ![GitHub tag (latest by date)](https://camo.githubusercontent.com/905f3fa05529f5a06a2df2c1a7a9da507aae32330f12b0b24f74a1ec9f539533/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f7461672f636961726569732f627970617373) ](https://packagist.org/packages/ciareis/bypass) [ ![Packagist Downloads](https://camo.githubusercontent.com/ee87b78cd0e8f57fdcddda7d0ea66bda9fc3466f277f154bddc2f9622969f8e5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f636961726569732f627970617373) ](https://packagist.org/packages/ciareis/bypass) [ ![Packagist License](https://camo.githubusercontent.com/4fe18b161d9a6b19690d3cffec60e7069789b4470409d8d6f7eaf504c1d2dd23/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f636961726569732f627970617373) ](https://github.com/ciareis/bypass/blob/main/LICENSE.md) [ ![Last Updated](https://camo.githubusercontent.com/0d43bdb9310e3f0ee2741d497d53cdc0560d836caac902f89daf81954525f8ca/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6173742d636f6d6d69742f636961726569732f627970617373) ](https://github.com/ciareis/bypass/commits/main)

---

About
-----

[](#about)

  Bypass for PHP provides a quick way to create a custom HTTP Server to return predefined responses to client requests.

This is useful in tests when your application makes requests to external services, and you need to simulate different situations like returning specific data or unexpected server errors.

Just open a Bypass server and configure your application/service to reach Bypass instead of the real-world API end point.

  ---

Requirements
------------

[](#requirements)

- **PHP**: 8.2 or higher (tested up to PHP 8.4)
- **Composer**: For dependency management

### Known Issues

[](#known-issues)

- **PHP 8.4**: You may see deprecation warnings from testing dependencies (Pest/PHPUnit) related to `ReflectionMethod::setAccessible()`. These warnings are harmless and come from the testing frameworks themselves, not from Bypass. They will be resolved when the dependencies are updated to support PHP 8.4 fully.

---

Installation
------------

[](#installation)

To install via [composer](https://getcomposer.org), run the following command:

```
composer require --dev ciareis/bypass
```

---

Video demo
----------

[](#video-demo)

[Pest and Bypass](https://youtube.com/watch?v=q_8kRlAIyms&t=2171s)

Writing Tests
-------------

[](#writing-tests)

### Content

[](#content)

- [Open Bypass Server](#1-open-a-bypass-server)
- [Bypass URL and Port](#2-bypass-url-and-port)
- [Routes](#3-routes)
    - [Standard Route](#31-standard-route)
    - [File Route](#32-file-route)
    - [Bypass Serve and Route Helpers](#33-bypass-serve-and-route-helpers)
- [Assert Route](#4-asserting-route-calling)
- [Stop or shut down](#5-stop-or-shut-down)

🔥 Check out full code examples [here](#examples) section.

### 1. Open a Bypass Server

[](#1-open-a-bypass-server)

To write a test, first open a Bypass server:

```
//Open a new Bypass server
$bypass = Bypass::open();
```

Bypass will always run at `http://localhost` listening to a random port number.

To specify a custom port, just pass it in the argument `(int) $port`.

```
//Open a new Bypass using port 8081
$bypass = Bypass::open(8081);
```

**Alternative method**: You can also use `Bypass::up()` which is an alias for `Bypass::open()`:

```
//Same as Bypass::open()
$bypass = Bypass::up();
```

### 2. Bypass URL and Port

[](#2-bypass-url-and-port)

You can retrieve the Bypass server URL using `getBaseUrl()`.

```
$bypassUrl = $bypass->getBaseUrl(); //http://localhost:16819
```

If you need to retrieve only the port number, use the `getPort()` method:

```
$bypassPort = $bypass->getPort(); //16819
```

### 3. Routes

[](#3-routes)

Bypass provides two types of routes: The `Standard Route` to return a text body content and the `File Route`, which returns a binary file.

When running your test suit, you should pass the URL created with Bypass to your service. In this way, you will make the service you are testing reach Bypass instead of reaching the real-world API end point.

#### 3.1 Standard Route

[](#31-standard-route)

```
use Ciareis\Bypass\Bypass;

//Json body
$body = '{"username": "john", "name": "John Smith", "total": 1250}';

//Route retuning the JSON body with HTTP Status 200
$bypass->addRoute(method: 'GET', uri: '/v1/demo/john', status: 200, body: $body);

//Instantiates a DemoService class
$service = new DemoService();

//Configure your service to access Bypass URL
$response = $service->setBaseUrl($bypass->getBaseUrl())
  ->getTotalByUser('john');

//Your test assertions here...
```

The method `addRoute()` accepts the following parameters:

ParameterTypeDescription**HTTP Method**`string $method`[HTTP Request Method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) (GET/POST/PUT/PATCH/DELETE)**URI**`string $uri`URI to be served by Bypass**Status**`int $status`[HTTP Status Code](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) to be returned by Bypass (default: 200)**Body**`string|array $body`Body to be served by Bypass (optional)**Times**`int $times`How many times the route should be called (default: 1)**Headers**`array $headers`Headers to be served by Bypass (optional)#### 3.2 File Route

[](#32-file-route)

```
use Ciareis\Bypass\Bypass;

//Reads a PDF file
$demoFile = \file_get_contents('storage/pdfs/demo.pdf');

//File Route returning a binary file with HTTP Status 200
$bypass->addFileRoute(method: 'GET', uri: '/v1/myfile', status: 200, file: $demoFile);

//Instantiates a DemoService class
$service = new DemoService();

//Configure your service to access Bypass URL
$response = $service->setBaseUrl($bypass->getBaseUrl())
  ->getPdf();

//Your test assertions here...
```

The method `addFileRoute()` accepts the following parameters:

ParameterTypeDescription**HTTP Method**`string $method`[HTTP Request Method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) (GET/POST/PUT/PATCH/DELETE)**URI**`string $uri`URI to be served by Bypass**Status**`int $status`[HTTP Status Code](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) to be returned by Bypass (default: 200)**File**`binary $file`Binary file to be served by Bypass**Times**`int $times`How many times the route should be called (default: 1)**Headers**`array $headers`Headers to be served by Bypass (optional)#### 3.3 Bypass Serve and Route Helpers

[](#33-bypass-serve-and-route-helpers)

Bypass provides you with convenient shortcuts to the most-common-used HTTP requests.

These shortcuts are called "Route Helpers" and are served automatically at a random port using `Bypass::serve()` without the need to call `Bypass::open()`.

In the next example, Bypasss serves two routes: A URL accessible by method `GET` returning a JSON body with status `200`, and a second route URL accessible by method `GET` and returning status `404`.

```
use Ciareis\Bypass\Bypass;
use Ciareis\Bypass\Route;

//Create and serve routes
$bypass = Bypass::serve(
  Route::ok(uri: '/v1/demo/john', body: ['username' => 'john', 'name' => 'John Smith', 'total' => 1250]), //method GET, status 200
  Route::notFound(uri: '/v1/demo/wally') //method GET, status 404
);

//Instantiates a DemoService class
$service = new DemoService();
$service->setBaseUrl($bypass->getBaseUrl());

//Consumes the "OK (200)" route
$responseOk = $service->getTotalByUser('john'); //200 - OK with total => 1250

//Consumes the "Not Found (404)" route
$responseNotFound = $service->getTotalByUser('wally'); //404 - Not found

//Your test assertions here...
```

#### Route Helpers

[](#route-helpers)

You may find below the list of Route Helpers.

Route HelperDefault MethodHTTP StatusBodyCommon usage**Route::ok()**GET200optional (string|array)Request was successful**Route::created()**POST201optional (string|array)Response to a POST request which resulted in a creation**Route::badRequest()**POST400optional (string|array)Something can't be parsed (ex: wrong parameter)**Route::unauthorized()**GET401optional (string|array)Not logged in**Route::forbidden()**GET403optional (string|array)Logged in but trying to request a restricted resource (without permission)**Route::notFound()**GET404optional (string|array)URL or resource does not exist**Route::notAllowed()**GET405optional (string|array)Method not allowed**Route::validationFailed()**POST422optional (string|array)Data sent does not satisfy validation rules**Route::tooMany()**GET429optional (string|array)Request rejected due to server limitation**Route::serverError()**GET500optional (string|array)General indication that something is wrong on the server sideYou may also adjust the helpers to your needs by passing arguments:

ParameterTypeDescription**URI**`string $uri`URI to be served by Bypass**Body**`string|array $body`Body to be served by Bypass (optional)**HTTP Method**`string $method`[HTTP Request Method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) (GET/POST/PUT/PATCH/DELETE)**Times**`int $times`How many times the route should be called (default: 1)**Headers**`array $headers`Headers to be served by Bypass (optional)In the example below, you can see the Helper `Route::badRequest` using method `GET` instead of its default method `POST`.

```
use Ciareis\Bypass\Bypass;
use Ciareis\Bypass\Route;

Bypass::serve(
  Route::badRequest(uri: '/v1/users?filter=foo', body: ['error' => 'Filter parameter foo is not allowed.'], method: 'GET')
);
```

📝 Note: Custom routes can be created using a [Standard Route](#31-standard-route) in case something you need is not covered by the Helpers.

### 4. Asserting Route Calling

[](#4-asserting-route-calling)

Sometimes you may need to assert that a route was called at least one or multiple times.

The method `assertRoutes()` will return a `RouteNotCalledException` if a route was NOT called as many times as defined in the `$times` parameter.

If you need to assert that a route is NOT being called by your service, set the parameter `$times = 0`

```
//Json body
$body = '{"username": "john", "name": "John Smith", "total": 1250}';

//Defines a route which must be called two times
$bypass->addRoute(method: 'GET', uri: '/v1/demo/john', status: 200, body: $body, times: 2);

//Instantiates a DemoService class
$service = new DemoService();

//Consumes the service using the Bypass URL
$response = $service->setBaseUrl($bypass->getBaseUrl())
  ->getTotalByUser('john');

$bypass->assertRoutes();

//Your test assertions here...
```

### 5. Stop or shut down

[](#5-stop-or-shut-down)

Bypass will automatically stop its server once your test is done running.

The Bypass server can be stopped or shut down at any point with the following methods:

To stop: `$bypass->stop();`

To shut down: `$bypass->down();`

API Reference
-------------

[](#api-reference)

### Methods

[](#methods)

#### Static Methods

[](#static-methods)

- **`Bypass::open(?int $port = null): self`**
    Opens a new Bypass server instance. If no port is specified, a random port will be used.
- **`Bypass::up(?int $port = null): self`**
    Alias for `open()`. Opens a new Bypass server instance.
- **`Bypass::serve(...$routes): self`**
    Creates and serves multiple routes at once. Accepts `Route` objects, `RouteFile` objects, or arrays.

#### Instance Methods

[](#instance-methods)

- **`addRoute(string $method, string $uri, int $status = 200, string|array|null $body = null, int $times = 1, array $headers = []): self`**
    Adds a standard route that returns text/JSON content.
- **`expect(string $method, string $uri, int $status = 200, string|array|null $body = null, int $times = 1, array $headers = []): self`**
    Alias for `addRoute()`. Adds a standard route that returns text/JSON content.
- **`addFileRoute(string $method, string $uri, int $status = 200, ?string $file = null, int $times = 1, array $headers = []): self`**
    Adds a file route that returns binary file content.
- **`getRoutes(): array`**
    Returns all registered routes as an array of route configurations.
- **`assertRoutes(): void`**
    Asserts that all registered routes were called the expected number of times. Throws `RouteNotCalledException` if any route was not called as expected.
- **`getBaseUrl(?string $path = null): string`**
    Returns the base URL of the Bypass server. Optionally appends a path.
- **`getPort(): int`**
    Returns the port number the Bypass server is listening on.
- **`stop(): self`**
    Stops the Bypass server by clearing all routes. The server process remains running.
- **`down(): self`**
    Shuts down the Bypass server process completely.

### Exceptions

[](#exceptions)

#### `RouteNotCalledException`

[](#routenotcalledexception)

Thrown when `assertRoutes()` is called and a route was not called the expected number of times.

```
use Ciareis\Bypass\RouteNotCalledException;

try {
    $bypass->assertRoutes();
} catch (RouteNotCalledException $e) {
    // Handle the exception
    // Message format: "Bypass expected route '/path' with method 'GET' to be called X times(s). Found Y calls(s) instead."
}
```

Examples
--------

[](#examples)

### Use case

[](#use-case)

To better illustrate Bypass usage, imagine you have to write a test for a service that calculates the total game score of a given username.

The score is obtained by making an external request to a fictitious API at `emtudo-games.com/v1/score/::USERNAME::`. The API returns HTTP Status `200` and a JSON body with a list of games:

```
{
  "games": [
    {
      "id": 1,
      "points": 25
    },
    {
      "id": 2,
      "points": 10
    }
  ],
  "is_active": true
}
```

```
use Ciareis\Bypass\Bypass;

//Opens a new Bypass server
$bypass = Bypass::open();

//Retrieves the Bypass URL
$bypassUrl = $bypass->getBaseUrl();

//Json body
$body = '{"games":[{"id":1, "name":"game 1","points":25},{"id":2, "name":"game 2","points":10}],"is_active":true}';

//Defines a route
$bypass->addRoute(method: 'GET', uri: '/v1/score/johndoe', status: 200, body: $body);

//Instantiates a TotalScoreService
$service = new TotalScoreService();

//Configure your service to access Bypass URL
$response = $service
  ->setBaseUrl($bypassUrl) // set the URL to the Bypass URL
  ->getTotalScoreByUsername('johndoe'); //returns 35

//Pest PHP verifies that response is 35
expect($response)->toBe(35);

//PHPUnit verifies that response is 35
$this->assertSame($response, 35);
```

### Quick Test Examples

[](#quick-test-examples)

Click below to see code snippets for [Pest PHP](https://pestphp.com) and PHPUnit.

Pest PHP```
use Ciareis\Bypass\Bypass;

it('properly returns the total score by username', function () {

  //Opens a new Bypass server
  $bypass = Bypass::open();

  //Json body
  $body = '{"games":[{"id":1, "name":"game 1","points":25},{"id":2, "name":"game 2","points":10}],"is_active":true}';

  //Defines a route
  $bypass->addRoute(method: 'GET', uri: '/v1/score/johndoe', status: 200, body: $body);

  //Configure your service to access Bypass URL
  $service = new TotalScoreService();
  $response = $service
    ->setBaseUrl($bypass->getBaseUrl())
    ->getTotalScoreByUsername('johndoe');

  //Verifies that response is 35
  expect($response)->toBe(35);
});

it('properly gets the logo', function () {

  //Opens a new Bypass server
  $bypass = Bypass::open();

  //Reads the file
  $filePath = 'docs/img/logo.png';
  $file = \file_get_contents($filePath);

  //Defines a route
  $bypass->addFileRoute(method: 'GET', uri: $filePath, status: 200, file: $file);

  //Configure your service to access Bypass URL
  $service = new LogoService();
  $response = $service->setBaseUrl($bypass->getBaseUrl())
    ->getLogo();

  // asserts
  expect($response)->toEqual($file);
});
```

PHPUnit```
use Ciareis\Bypass\Bypass;

class BypassTest extends TestCase
{
  public function test_total_score_by_username(): void
  {
    //Opens a new Bypass server
    $bypass = Bypass::open();

    //Json body
    $body = '{"games":[{"id":1,"name":"game 1","points":25},{"id":2,"name":"game 2","points":10}],"is_active":true}';

    //Defines a route
    $bypass->addRoute(method: 'GET', uri: '/v1/score/johndoe', status: 200, body: $body);

    //Configure your service to access Bypass URL
    $service = new TotalScoreService();
    $response = $service
      ->setBaseUrl($bypass->getBaseUrl())
      ->getTotalScoreByUsername('johndoe');

    //Verifies that response is 35
    $this->assertSame(35, $response);
  }

  public function test_gets_logo(): void
  {
    //Opens a new Bypass server
    $bypass = Bypass::open();

    //Reads the file
    $filePath = 'docs/img/logo.png';
    $file = \file_get_contents($filePath);

    //Defines a route
    $bypass->addFileRoute(method: 'GET', uri: $filePath, status: 200, file: $file);

    //Configure your service to access Bypass URL
    $service = new LogoService();
    $response = $service->setBaseUrl($bypass->getBaseUrl())
      ->getLogo();

    $this->assertSame($response, $file);
  }
}
```

### Test Examples

[](#test-examples)

📚 See Bypass being used in complete tests with [Pest PHP](https://github.com/ciareis/bypass/blob/main/tests/BypassPestTest.php) and [PHPUnit](https://github.com/ciareis/bypass/blob/main/tests/BypassPhpUnitTest.php) for the [GithubRepoService](https://github.com/ciareis/bypass/blob/main/tests/Services/GithubRepoService.php) demo service.

### Advanced Examples

[](#advanced-examples)

#### Using Custom Headers

[](#using-custom-headers)

```
use Ciareis\Bypass\Bypass;

$bypass = Bypass::open();

$bypass->addRoute(
    method: 'GET',
    uri: '/v1/api/data',
    status: 200,
    body: ['data' => 'example'],
    headers: [
        'X-Custom-Header' => 'value',
        'X-Another-Header' => ['value1', 'value2'], // Multiple values
    ]
);
```

#### Multiple Route Calls

[](#multiple-route-calls)

```
use Ciareis\Bypass\Bypass;

$bypass = Bypass::open();

// Route must be called exactly 3 times
$bypass->addRoute(
    method: 'GET',
    uri: '/v1/api/data',
    status: 200,
    body: ['data' => 'example'],
    times: 3
);

$service = new ApiService();
$service->setBaseUrl($bypass->getBaseUrl());

// Call the route 3 times
$service->fetchData();
$service->fetchData();
$service->fetchData();

// This will pass
$bypass->assertRoutes();
```

#### Handling Exceptions

[](#handling-exceptions)

```
use Ciareis\Bypass\Bypass;
use Ciareis\Bypass\RouteNotCalledException;

$bypass = Bypass::open();
$bypass->addRoute(method: 'GET', uri: '/v1/api/data', status: 200);

$service = new ApiService();
$service->setBaseUrl($bypass->getBaseUrl());

// Don't call the route

try {
    $bypass->assertRoutes();
    $this->fail('Expected RouteNotCalledException');
} catch (RouteNotCalledException $e) {
    $this->assertStringContainsString("expected route '/v1/api/data'", $e->getMessage());
}
```

Troubleshooting
---------------

[](#troubleshooting)

### Common Issues

[](#common-issues)

#### Port Already in Use

[](#port-already-in-use)

If you specify a port that's already in use, Bypass will fail to start. Use a random port (default) or ensure the port is available:

```
// Use random port (recommended)
$bypass = Bypass::open();

// Or specify a port and handle errors
try {
    $bypass = Bypass::open(8080);
} catch (RuntimeException $e) {
    // Port might be in use, try another
    $bypass = Bypass::open(8081);
}
```

#### Server Timeout

[](#server-timeout)

Bypass has a default timeout of 5 seconds for server startup. If your system is slow, the server might not start in time. This is rare but can happen in CI environments.

#### Route Not Found Errors

[](#route-not-found-errors)

If you're getting "route not found" errors, ensure:

- The URI matches exactly (including query parameters)
- The HTTP method matches (GET, POST, etc.)
- The route was added before making the request
- The service is using the correct Bypass URL

#### Getting All Registered Routes

[](#getting-all-registered-routes)

You can inspect all registered routes for debugging:

```
$bypass = Bypass::open();
$bypass->addRoute(method: 'GET', uri: '/v1/api/data', status: 200);

$routes = $bypass->getRoutes();
// Returns array with route configurations
```

Contributing
------------

[](#contributing)

We welcome contributions! Here's how you can help:

1. **Fork the repository**
2. **Create a feature branch**: `git checkout -b feature/amazing-feature`
3. **Make your changes** following the existing code style
4. **Add tests** for new functionality
5. **Ensure all tests pass**: `vendor/bin/pest`
6. **Commit your changes**: `git commit -m 'Add amazing feature'`
7. **Push to the branch**: `git push origin feature/amazing-feature`
8. **Open a Pull Request**

### Code Style

[](#code-style)

- Follow PSR-12 coding standards
- Use type hints where possible
- Add PHPDoc comments for public methods
- Write tests for new features

### Testing

[](#testing)

- Run tests with: `vendor/bin/pest`
- Ensure all tests pass before submitting a PR
- Add tests for any new functionality

Credits
-------

[](#credits)

- [Leandro Henrique](https://github.com/emtudo)
- [All Contributors](../../contributors)

And a special thanks to [@DanSysAnalyst](https://github.com/dansysanalyst)

### Inspired

[](#inspired)

Code inspired by [Bypass](https://github.com/PSPDFKit-labs/bypass)

###  Health Score

59

—

FairBetter than 99% of packages

Maintenance78

Regular maintenance activity

Popularity41

Moderate usage in the ecosystem

Community27

Small or concentrated contributor base

Maturity76

Established project with proven stability

 Bus Factor1

Top contributor holds 75% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~47 days

Recently: every ~74 days

Total

37

Last Release

116d ago

Major Versions

v0.7.0 → v1.0.02021-08-04

v1.2.1 → v2.0.02025-03-19

v2.1.2 → v3.0.02026-01-22

PHP version history (4 changes)0.0.1PHP &gt;=7.2.5

v0.1.2PHP ^8.0

v1.2.0PHP ^8.1

v2.0.2PHP ^8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/191396?v=4)[Leandro Henrique Reis](/maintainers/emtudo)[@emtudo](https://github.com/emtudo)

---

Top Contributors

[![emtudo](https://avatars.githubusercontent.com/u/191396?v=4)](https://github.com/emtudo "emtudo (165 commits)")[![dansysanalyst](https://avatars.githubusercontent.com/u/79267265?v=4)](https://github.com/dansysanalyst "dansysanalyst (33 commits)")[![vluzrmos](https://avatars.githubusercontent.com/u/450848?v=4)](https://github.com/vluzrmos "vluzrmos (7 commits)")[![vs0uz4](https://avatars.githubusercontent.com/u/2080547?v=4)](https://github.com/vs0uz4 "vs0uz4 (6 commits)")[![ramsey](https://avatars.githubusercontent.com/u/42941?v=4)](https://github.com/ramsey "ramsey (3 commits)")[![nunomaduro](https://avatars.githubusercontent.com/u/5457236?v=4)](https://github.com/nunomaduro "nunomaduro (3 commits)")[![otilor](https://avatars.githubusercontent.com/u/39733548?v=4)](https://github.com/otilor "otilor (2 commits)")[![mateusjatenee](https://avatars.githubusercontent.com/u/10816999?v=4)](https://github.com/mateusjatenee "mateusjatenee (1 commits)")

---

Tags

apiapi-fakerbypass-serverbypass-servicefakerroutes-bypass-serveshttpphpapifakerbypass

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/ciareis-bypass/health.svg)

```
[![Health](https://phpackages.com/badges/ciareis-bypass/health.svg)](https://phpackages.com/packages/ciareis-bypass)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
