Sobre | Instalação | Escrevendo Testes | Exemplos | Créditos | Inspiração
O Bypass para PHP prove uma maneira rápida para você criar um Servidor HTTP personalizado que retorne resposta predefinidas as requisições do seu cliente. Ele é muito útil em ambientes de testes, onde seu aplicativo realiza requisições HTTP a serviços externos e você precisa simular diferentes situações, como por exemplo retornar dados específicos ou erros inesperados do servidor. |
📌 O Bypass requer o uso do PHP 8.0 ou superior.
Para instalar o Bypass através do composer, execute o seguinte comando:
composer require --dev ciareis/bypass
Testando nota fácil.io com Laravel e Bypass: Meu próprio sandbox
- Abrindo o Servidor Bypass
- Recuperando o URL do Bypass
- Rotas
- Verificando se Rotas foram Chamadas
- Parar ou Encerrar
📝 Nota: Se você deseja visualizar os códigos completos, vá para a seção Exemplos.
Para escrever um teste, primeiro abra uma instância do servidor Bypass:
//Abre uma nova instância do Bypass Server
$bypass = Bypass::open();
O Bypass sempre será executado no URL http://localhost
escutando através de uma porta de número aleatório.
Caso seja necessário, a porta pode ser especificada passando um valor para o argumento (int) $port
:
//Abre uma nova instância do Bypass Server na porta 8081
$bypass = Bypass::open(8081);
O endereço(URL) e porta do servidor pode ser recuperados usando o método getBaseUrl()
:
$bypassUrl = $bypass->getBaseUrl(); //http://localhost:16819
Se você precisar recuperar apenas o número da porta, utilize o método getPort()
:
$bypassPort = $bypass->getPort(); //16819
O Bypass serve dois tipos de rotas: A Rota Padrão
, que pode retornar uma corpo de texto em formato JSON e a Rota de Arquivo
, que pode retornar um arquivo binário.
Ao executar seus testes, você informará as rotas do Bypass para o seu aplicativo ou serviço, fazendo com que ele acesse os URLs do Bypass ao invés dos URLs do mundo real.
use Ciareis\Bypass\Bypass;
//Corpo de texto em formato JSON
$body = '{"username": "john", "name": "John Smith", "total": 1250}';
//Define uma rota que deverá retornar um corpo de texto (JSON) e o código HTTP 200
$bypass->addRoute(method: 'get', uri: '/v1/demo', status: 200, body: $body);
//Instânciando a classe DemoService
$service = new DemoService();
//Consumindo o serviço utilizando o URL fornecido pelo Bypass
$response = $service->setBaseUrl($bypass->getBaseUrl())
->getTotal();
//Suas asserções/verificações de teste ficam aqui...
O método addRoute()
aceita os seguintes parâmetros:
Parâmetro | Tipo | Descrição |
---|---|---|
HTTP Method | int $method |
Método de Requisição HTTP (GET/POST/PUT/PATCH/DELETE) |
URI | string $uri |
URI a ser servido pelo Bypass |
Status | int $status |
Código de Status HTTP a ser retornado pelo Bypass (padrão: 200) |
Body | string|array $body |
Corpo de texto (JSON) a ser servido pelo Bypass (opcional) |
Times | int $times |
Quantidade de vezes em que a rota deve ser acessada (padrão: 1) |
use Ciareis\Bypass\Bypass;
//Lendo um arquivo PDF
$demoFile = \file_get_contents('storage/pdfs/demo.pdf');
//Define uma rota que deverá retornar o arquivo PDF e o código HTTP 200
$bypass->addFileRoute(method: 'get', uri: '/v1/myfile', status: 200, file: $demoFile);
//Instânciando a classe DemoService
$service = new DemoService();
//Consumindo o serviço utilizando o URL fornecido pelo Bypass
$response = $service->setBaseUrl($bypass->getBaseUrl())
->getPdf();
//Suas asserções/verificações de teste ficam aqui...
O método addFileRoute()
aceita os seguintes parâmetros:
Parâmetro | Tipo | Descrição |
---|---|---|
HTTP Method | int $method |
Método de Requisição HTTP (GET/POST/PUT/PATCH/DELETE) |
URI | string $uri |
URI a ser servido pelo Bypass |
Status | int $status |
Código de Status HTTP a ser retornado pelo Bypass (padrão: 200) |
File | binary $file |
Arquivo binário a ser servidor pelo Bypass |
Times | int $times |
Quantidade de vezes em que a rota deve ser acessada (padrão: 1) |
O Bypass vem com uma variedade de atalhos convenientes para as requisições HTTP mais utilizadas.
Esses atalhos são chamados de "Métodos Assistentes para Rotas", porém para simplificar vamos chama-los apenas de "Assistentes para Rotas". Eles são servidos em uma porta aleatória e de forma automatica ao utilizarmos: Bypass::serve()
.
Ao servirmos as rotas por meio dos assistentes, não necessitamos de chamar Bypass::open()
.
Exemplo:
use Ciareis\Bypass\Bypass;
use Ciareis\Bypass\Route;
//Criando as rotas na inicialização do servidor
$bypass = Bypass::serve(
Route::ok(uri: '/v1/demo/john', body: ['username' => 'john', 'name' => 'John Smith', 'total' => 1250]), //método GET, código HTTP 200
Route::notFound(uri: '/v1/demo/wally') //método GET, código HTTP 404
);
//Instânciando a classe DemoService
$service = new DemoService();
$service->setBaseUrl($bypass->getBaseUrl());
//Consumindo o serviço utilizando a rota "OK (200)"
$responseOk = $service->getTotalByUser('john'); //200 - OK com total => 1250
//Consumindo o serviço utilizando a rota "Not Found (404)"
$responseNotFound = $service->getTotalByUser('wally'); //404 - Recurso não encontrado
//Suas asserções/verificações de teste ficam aqui...
No exemplo acima, o Bypass está servindo duas rotas: Uma rota acessível pelo método HTTP GET
retornando um JSON com o código HTTP 200
, e uma segunda rota sendo acessível também pelo método GET
só que retornando apemas um código HTTP 404
.
Método Assistente | Método HTTP | Codigo HTTP | Retorno | Uso comum |
---|---|---|---|---|
Route::ok() | GET | 200 | opcional (string|array) | Requisição foi bem sucedida |
Route::created() | POST | 201 | opcional (string|array) | Resposta a uma requisição em que resultou uma criação |
Route::badRequest() | POST | 400 | opcional (string|array) | Algo incorreto na requisição (ex: parâmetro errado) |
Route::unauthorized() | GET | 401 | opcional (string|array) | Não autenticado |
Route::forbidden() | GET | 403 | opcional (string|array) | Autenticado, porém tentando acessar um recurso restrito (sem permissão) |
Route::notFound() | GET | 404 | opcional (string|array) | URL ou recurso não existem |
Route::notAllowed() | GET | 405 | opcional (string|array) | Método não permitido |
Route::validationFailed() | POST | 422 | opcional (string|array) | Os dados enviados não satisfazem as regras de validação |
Route::tooMany() | GET | 429 | opcional (string|array) | Requisição rejeitada devido a limitações do servidor |
Route::serverError() | GET | 500 | opcional (string|array) | Geralmente indica que algum erro aconteceu no lado do servidor |
Você também pode personalizar os assistentes de rotas de acordo com as suas necessidades, passando os seguintes parâmetros:
Parâmetro | Tipo | Descrição |
---|---|---|
URI | string $uri |
URI a ser servido pelo Bypass |
Body | string|array $body |
Corpo de texto (JSON) a ser servido pelo Bypass (opcional) |
HTTP Method | string $method |
Método de Requisição HTTP (GET/POST/PUT/PATCH/DELETE) |
Times | int $times |
Quantidade de vezes em que a rota deve ser acessada (padrão: 1) |
No exemplo abaixo, você pode ver o assistente Route::badRequest
usando o método GET
ao invés do método POST
.
use Ciareis\Bypass\Bypass;
use Ciareis\Bypass\Route;
Bypass::serve(
Route::badRequest(uri: '/v1/users?filter=foo', body: ['error' => 'O parâmetro filter de valor foo não existe.'], method: 'GET')
);
📝 Nota: Rotas personalizadas podem ser criadas usando uma Rota Padrão no caso de você necessitar de algo mais específico, não coberto pelos assistentes de rotas.
Você pode necessitar de verificar se uma rota foi acessada uma ou mais vezes.
O método assertRoutes()
retorna um RouteNotCalledException
no caso de uma rota não ter sido acessada por tantas vezes quanto forão definidas no parâmetro $times
.
Se você precisa garantir que uma rota não está sendo acessada pelo seu serviço, defina o parâmetro como zero $times = 0
.
//Corpo de texto em formato JSON
$body = '{"username": "john", "name": "John Smith", "total": 1250}';
//Define uma rota que deve ser acessada duas vezes
$bypass->addRoute(method: 'get', uri: '/v1/demo', status: 200, body: $body, times: 2);
//Instânciando a classe DemoService
$service = new DemoService();
//Consumindo o serviço utilizando o URL fornecido pelo Bypass
$response = $service->setBaseUrl($bypass->getBaseUrl())
->getTotal();
$bypass->assertRoutes();
//Suas asserções/verificações de teste ficam aqui...
O Bypass vai encerrar seu próprio servidor uma vez que seu teste termine de ser executado.
O servidor do Bypass pode ser interrompido ou encerrado a qualquer momento com os seguintes métodos:
Para interromper:
$bypass->stop();
Para encerrar:
$bypass->down();
Para ilustrar melhor o uso do Bypass, imagine que você necessita escrever testes para um serviço chamado de TotalScoreService
. Este serviço calcula a pontução total de um usuário específico em um jogo através de seu nome de usuário.
Para obter a pontuação, este serviço deve consumir uma API fictícia hospedada no endereço emtudo-games.com/v1/score/::USERNAME::
. A API consumida retorna o código de status HTTP 200
e um JSON contendo em seu corpo a lista de jogos:
{
"games": [
{
"id": 1,
"points": 25
},
{
"id": 2,
"points": 10
}
],
"is_active": true
}
use Ciareis\Bypass\Bypass;
//Abre uma nova instância do Bypass Server
$bypass = Bypass::open();
//Recuperando o URL de acesso do Bypass Server
$bypassUrl = $bypass->getBaseUrl();
//Corpo de texto em formato JSON
$body = '{"games":[{"id":1,"name":"game 1","points":25},{"id":2,"name":"game 2","points":10}],"is_active":true}';
//Definição de rota a ser acessada pelo serviço
$bypass->addRoute(method: 'get', uri: '/v1/score/johndoe', status: 200, body: $body);
//Instânciando TotalScoreService
$service = new TotalScoreService();
//Consumindo o serviço utilizando a URL retornada pelo Bypass Server
$response = $service
->setBaseUrl($bypassUrl) //Define o URL a ser usado pelo serviço
->getTotalScoreByUsername('johndoe'); //retorna 35
//Verificando se a resposta é 35 com Pest PHP
expect($response)->toBe(35);
//Verificando se a resposta é 35 com PHPUnit
$this->assertSame($response, 35);
Clique abaixo para visualizar trechos de códigos utilizando Pest PHP e PHPUnit.
Pest PHP
use Ciareis\Bypass\Bypass;
it('properly returns the total score by username', function () {
//Abre uma nova instância do Bypass Server
$bypass = Bypass::open();
//Corpo de texto em formato JSON
$body = '{"games":[{"id":1,"name":"game 1","points":25},{"id":2,"name":"game 2","points":10}],"is_active":true}';
//Definição de rota a ser acessada pelo serviço
$bypass->addRoute(method: 'get', uri: '/v1/score/johndoe', status: 200, body: $body);
//Instânciando e consumindo o serviço utilizando a URL retornada pelo Bypass Server
$service = new TotalScoreService();
$response = $service
->setBaseUrl($bypass->getBaseUrl())
->getTotalScoreByUsername('johndoe');
//Verifica se a resposta é 35
expect($response)->toBe(35);
});
it('properly gets the logo', function () {
//Abre uma nova instância do Bypass Server
$bypass = Bypass::open();
//Lendo arquivo a partir do caminho informado
$filePath = 'docs/img/logo.png';
$file = \file_get_contents($filePath);
//Definição de rota a ser acessada pelo serviço
$bypass->addFileRoute(method: 'get', uri: $filePath, status: 200, file: $file);
//Instânciando e consumindo o serviço utilizando a URL retornada pelo Bypass Server
$service = new LogoService();
$response = $service->setBaseUrl($bypass->getBaseUrl())
->getLogo();
//Verifica se o arquivo retornado pelo Bypass é igual ao arquivo local
expect($response)->toEqual($file);
});
PHPUnit
use Ciareis\Bypass\Bypass;
class BypassTest extends TestCase
{
public function test_total_score_by_username(): void
{
//Abre uma nova instância do Bypass Server
$bypass = Bypass::open();
//Corpo de texto em formato JSON
$body = '{"games":[{"id":1,"name":"game 1","points":25},{"id":2,"name":"game 2","points":10}],"is_active":true}';
//Definição de rota a ser acessada pelo serviço
$bypass->addRoute(method: 'get', uri: '/v1/score/johndoe', status: 200, body: $body);
//Instânciando e consumindo o serviço utilizando a URL retornada pelo Bypass Server
$service = new TotalScoreService();
$response = $service
->setBaseUrl($bypass->getBaseUrl())
->getTotalScoreByUsername('johndoe');
//Verifica se a resposta é 35
$this->assertSame(35, $response);
}
public function test_gets_logo(): void
{
//Abre uma nova instância do Bypass Server
$bypass = Bypass::open();
//Lendo arquivo a partir do caminho informado
$filePath = 'docs/img/logo.png';
$file = \file_get_contents($filePath);
//Definição de rota a ser acessada pelo serviço
$bypass->addFileRoute(method: 'get', uri: $filePath, status: 200, file: $file);
//Instânciando e consumindo o serviço utilizando a URL retornada pelo Bypass Server
$service = new LogoService();
$response = $service->setBaseUrl($bypass->getBaseUrl())
->getLogo();
//Verifica se o arquivo retornado pelo Bypass é igual ao arquivo local
$this->assertSame($response, $file);
}
}
📚 Veja exemplos completos de utilização do Bypass em testes com Pest PHP e PHPUnit para o exemplo de serviço GithubRepoService.
E um agradecimento especial para o @DanSysAnalyst
Código inspirado no pacote Bypass