Testing HTTP requests com Http::fake
1. Introdução ao Http::fake no Laravel
Ao testar aplicações Laravel que consomem APIs externas, um dos maiores desafios é garantir que os testes sejam rápidos, determinísticos e independentes de serviços terceiros. O Http::fake() surge como uma ferramenta essencial nesse cenário.
Http::fake() é um método do fachada Http do Laravel que intercepta todas as requisições HTTP realizadas pelo Http Client durante os testes, permitindo que você retorne respostas simuladas sem nunca tocar no servidor real. A diferença fundamental entre testes com requisições reais e simuladas está na previsibilidade: enquanto requisições reais podem falhar por motivos alheios ao seu código (rede, servidor fora do ar, limite de taxa), as simuladas garantem que você está testando exclusivamente a lógica da sua aplicação.
O ecossistema de testing HTTP no Laravel é robusto, oferecendo desde fakes simples até sequências complexas de respostas, matching por URL, verificação de requisições enviadas e simulação de cenários de erro.
2. Configuração básica e primeiros passos
Para usar Http::fake(), você precisa do Laravel 7 ou superior com o pacote HTTP Client, que já vem incluído por padrão. Não há instalação adicional necessária.
A sintaxe fundamental é simples:
<?php
namespace Tests\Unit;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class ExemploBasicoTest extends TestCase
{
public function test_requisicao_simples()
{
Http::fake();
$response = Http::get('https://api.exemplo.com/usuarios');
$this->assertTrue($response->ok());
}
}
Neste exemplo, qualquer requisição HTTP feita durante o teste retornará uma resposta vazia com status 200. Para verificar se a requisição foi feita corretamente:
public function test_verifica_requisicao_enviada()
{
Http::fake();
Http::get('https://api.exemplo.com/usuarios');
Http::assertSent(function ($request) {
return $request->url() === 'https://api.exemplo.com/usuarios' &&
$request->method() === 'GET';
});
}
3. Trabalhando com respostas personalizadas
Para simular cenários realistas, você precisa controlar status codes, headers e body. Use Http::response() para isso:
public function test_resposta_personalizada()
{
Http::fake([
'api.exemplo.com/*' => Http::response([
'id' => 1,
'nome' => 'João Silva'
], 200, ['X-Custom-Header' => 'valor'])
]);
$response = Http::get('https://api.exemplo.com/usuarios/1');
$this->assertEquals(200, $response->status());
$this->assertEquals('João Silva', $response->json('nome'));
}
Para simular erros:
public function test_erro_404()
{
Http::fake([
'api.exemplo.com/usuarios/999' => Http::response('Não encontrado', 404)
]);
$response = Http::get('https://api.exemplo.com/usuarios/999');
$this->assertTrue($response->notFound());
}
public function test_erro_de_rede()
{
Http::fake([
'api.exemplo.com/*' => function () {
throw new \Illuminate\Http\Client\ConnectionException('Timeout');
}
]);
$this->expectException(\Illuminate\Http\Client\ConnectionException::class);
Http::get('https://api.exemplo.com/dados');
}
4. Estratégias de matching de URLs
O Http::fake() aceita um array onde as chaves são padrões de URL e os valores são as respostas. Use curingas (*) para grupos de endpoints:
public function test_matching_por_url()
{
Http::fake([
// Fake global para qualquer URL não especificada
'*' => Http::response('fallback', 200),
// Fake específico para um endpoint
'api.github.com/*' => Http::response(['login' => 'usuario'], 200),
// Fake com closure para validação dinâmica
'api.exemplo.com/pedidos' => function ($request) {
if ($request->method() === 'POST') {
return Http::response(['id' => 123], 201);
}
return Http::response([], 200);
}
]);
$this->assertEquals('fallback', Http::get('https://outro.com')->body());
$this->assertEquals('usuario', Http::get('https://api.github.com/user')->json('login'));
}
A ordem de precedência importa: padrões mais específicos têm prioridade sobre curingas. Se nenhum padrão corresponder, o Laravel usa o fake global.
5. Testando cenários complexos
Para simular APIs com autenticação:
public function test_api_com_token()
{
Http::fake([
'api.exemplo.com/*' => function ($request) {
if ($request->hasHeader('Authorization')) {
return Http::response(['dados' => 'protegidos'], 200);
}
return Http::response('Não autorizado', 401);
}
]);
$response = Http::withToken('meu-token')->get('https://api.exemplo.com/dados');
$this->assertEquals(200, $response->status());
}
Para múltiplas requisições em sequência:
public function test_sequencia_de_requisicoes()
{
Http::fake([
'api.exemplo.com/*' => Http::sequence()
->push(['status' => 'processando'], 202)
->push(['status' => 'concluido'], 200)
->pushStatus(500)
]);
$primeira = Http::post('https://api.exemplo.com/tarefas');
$this->assertEquals(202, $primeira->status());
$segunda = Http::get('https://api.exemplo.com/tarefas/1');
$this->assertEquals('concluido', $segunda->json('status'));
$terceira = Http::get('https://api.exemplo.com/tarefas/2');
$this->assertTrue($terceira->failed());
}
Para testar timeouts:
public function test_timeout()
{
Http::fake([
'api.lenta.com/*' => Http::response()->withDelay(5000)
]);
$this->expectException(\Illuminate\Http\Client\ConnectionException::class);
Http::timeout(2)->get('https://api.lenta.com/dados');
}
6. Asserções e verificação de comportamento
O Laravel oferece métodos poderosos para verificar o comportamento das requisições:
public function test_assertions_detalhadas()
{
Http::fake();
Http::post('https://api.exemplo.com/usuarios', [
'nome' => 'Maria',
'email' => 'maria@exemplo.com'
]);
Http::get('https://api.exemplo.com/usuarios');
// Verifica se uma requisição foi enviada
Http::assertSent(function ($request) {
return $request->url() === 'https://api.exemplo.com/usuarios' &&
$request->method() === 'POST' &&
$request['nome'] === 'Maria';
});
// Verifica se NÃO foi enviada
Http::assertNotSent(function ($request) {
return $request->url() === 'https://api.exemplo.com/admin';
});
// Verifica quantidade de requisições
Http::assertSentCount(2);
// Verifica ordem com sequence
Http::assertSentInOrder([
function ($request) { return $request->method() === 'POST'; },
function ($request) { return $request->method() === 'GET'; }
]);
}
Para inspecionar logs de fakes:
public function test_debug_fake()
{
Http::fake();
Http::get('https://api.exemplo.com');
$recorded = Http::recorded();
$this->assertCount(1, $recorded);
$this->assertEquals('https://api.exemplo.com', $recorded[0][0]->url());
}
7. Boas práticas e integração com testes existentes
Organize seus fakes em métodos auxiliares para reutilização:
trait FakeHttpResponses
{
protected function fakeApiExternaComSucesso(): void
{
Http::fake([
'api.externa.com/pagamentos' => Http::response([
'id' => 'pag_123',
'status' => 'aprovado'
], 200),
'api.externa.com/*' => Http::response([], 200)
]);
}
protected function fakeApiExternaComFalha(): void
{
Http::fake([
'api.externa.com/*' => Http::response('Serviço indisponível', 503)
]);
}
}
Lembre-se de limpar o estado entre testes no tearDown():
protected function tearDown(): void
{
Http::assertNothingSent(); // Opcional: garante que nada vazou
parent::tearDown();
}
Combine com Data Providers do PHPUnit:
/**
* @dataProvider provedorStatusPagamento
*/
public function test_pagamento_com_diferentes_status(string $status, int $codigo)
{
Http::fake([
'gateway.com/*' => Http::response(['status' => $status], $codigo)
]);
$servico = new PagamentoService();
$resultado = $servico->processar(100.00);
$this->assertEquals($status, $resultado['status']);
}
public function provedorStatusPagamento(): array
{
return [
['aprovado', 200],
['recusado', 200],
['pendente', 202],
['erro_servidor', 500],
];
}
8. Exemplos práticos do mundo real
Gateway de pagamento:
public function test_pagamento_aprovado()
{
Http::fake([
'gateway.pagamento.com/transacoes' => Http::response([
'id' => 'txn_abc123',
'status' => 'approved',
'valor' => 150.00
], 200)
]);
$pagamento = new PagamentoService();
$resultado = $pagamento->cobrar(150.00, 'cartao_credito');
$this->assertTrue($resultado->aprovado());
$this->assertEquals('txn_abc123', $resultado->idTransacao());
}
API de clima com fallback:
public function test_busca_clima_com_fallback()
{
Http::fake([
'api.weather.com/*' => Http::sequence()
->pushStatus(500) // Primeira tentativa falha
->push(['temp' => 25, 'cidade' => 'São Paulo'], 200) // Fallback funciona
]);
$clima = new ClimaService();
$resultado = $clima->obterTemperatura('São Paulo');
$this->assertEquals(25, $resultado);
}
Serviço de e-mail transacional:
public function test_envio_email_transacional()
{
Http::fake([
'api.sendgrid.com/v3/mail/send' => function ($request) {
$body = $request->body();
$dados = json_decode($body, true);
$this->assertArrayHasKey('personalizations', $dados);
$this->assertEquals('cliente@exemplo.com', $dados['personalizations'][0]['to'][0]['email']);
return Http::response(['message' => 'success'], 202);
}
]);
$servico = new EmailService();
$resultado = $servico->enviarEmailBemVindo('cliente@exemplo.com');
$this->assertTrue($resultado);
}
Referências
- Laravel HTTP Client Testing Documentation — Documentação oficial do Laravel sobre Http::fake, com exemplos detalhados de todas as funcionalidades
- Laravel News - Testing HTTP Requests with Http::fake — Artigo técnico com exemplos práticos de cenários comuns de teste
- Laracasts - HTTP Testing in Laravel — Série de vídeos tutoriais sobre como testar requisições HTTP no Laravel
- Laravel Daily - Http::fake Deep Dive — Guia completo com exemplos avançados de matching e sequências
- Laravel Bootcamp - Testing External APIs — Tutorial oficial do Laravel Bootcamp focado em testes de APIs externas
- Stack Overflow - Http::fake Best Practices — Discussões da comunidade sobre boas práticas e armadilhas comuns