ciareis/bypass

Bypass for PHP 提供了一种快速方式,用于创建一个自定义 HTTP 服务器,而不是实际的服务器,以返回预制的响应给客户端请求。这在测试中非常有用,当你想要创建一个模拟 HTTP 服务器并测试你的 HTTP 客户端如何处理来自服务器不同类型的响应时。

v1.2.1 2024-03-19 20:17 UTC

README

Bypass Logo

Bypass for PHP

关于 | 安装 | 编写测试 | 示例 | 鸣谢 | 灵感来源

US flag in base64 BR flag in base64

Tests GitHub tag (latest by date) Packagist Downloads Packagist License Last Updated

关于

安装

📌 Bypass 需要 PHP 8.0+。

通过 composer 安装,请运行以下命令

composer require --dev ciareis/bypass

视频演示

Pest 和 Bypass

编写测试

内容

🔥 在以下部分查看完整的代码示例 这里

1. 打开 Bypass 服务器

要编写测试,首先打开 Bypass 服务器

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

Bypass 将始终在 http://localhost 上运行,监听随机端口。

要指定自定义端口,只需在参数 (int) $port 中传递它。

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

2. Bypass URL 和端口

您可以使用 getBaseUrl() 获取 Bypass 服务器 URL。

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

如果您只需要获取端口号,请使用 getPort() 方法

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

3. 路由

Bypass 提供两种类型的路由:用于返回文本正文内容的 标准路由 和返回二进制文件的 文件路由

在运行测试套件时,您应该将 Bypass 创建的 URL 传递给您的服务。这样,您将使您正在测试的服务达到 Bypass 而不是达到现实世界的 API 终端。

3.1 标准路由

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...

addRoute() 方法接受以下参数

3.2 文件路由

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...

addFileRoute() 方法接受以下参数

3.3 Bypass Serve 和路由助手

Bypass 为您提供便捷的快捷方式,用于最常见的 HTTP 请求。

这些快捷方式称为“路由助手”,并使用 Bypass::serve() 在随机端口上自动提供,无需调用 Bypass::open()

在下一个示例中,Bypass 提供了两个路由:一个可通过 GET 方法访问的 URL,返回状态为 200 的 JSON 正文,以及一个第二个可通过 GET 方法访问的路由 URL,返回状态 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...

路由助手

以下列出了路由助手列表。

您还可以通过传递参数来根据您的需求调整助手

在下面的示例中,您可以看到使用方法 GET 的助手 Route::badRequest 而不是其默认方法 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')
);

📝 注意:如果助手未涵盖您需要的某些内容,可以使用 标准路由 创建自定义路由。

4. 断言路由调用

有时您可能需要断言某个路由至少被调用一次或多次。

方法 assertRoutes() 如果某个路由未被调用次数与在 $times 参数中定义的次数不符,将返回 RouteNotCalledException

如果您需要断言您的服务没有调用某个路由,请设置参数 $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. 停止或关闭

Bypass 在测试完成后将自动停止其服务器。

可以使用以下方法在任何时候停止或关闭 Bypass 服务器

停止: $bypass->stop();

关闭: $bypass->down();

示例

用例

为了更好地说明 Bypass 的使用,假设您需要编写一个测试,该测试针对一个计算给定用户名的游戏总分数的服务。

分数是通过向位于 emtudo-games.com/v1/score/::USERNAME:: 的虚构 API 发送外部请求获得的。API 返回 HTTP 状态码 200 和包含游戏列表的 JSON 主体

{
  "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 = $serivce
  ->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);

快速测试示例

点击下面查看 Pest PHP 和 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);
  }
}

测试示例

📚 在完整的测试中查看 Bypass 的使用,请参阅 Pest PHPPHPUnit,以及 GithubRepoService 演示服务。

致谢

并向 @DanSysAnalyst 表示特别感谢

灵感来源

代码灵感来源于 Bypass