<h2>Entendendo Testes de Integração em Laravel</h2>
<p>Testes de integração verificam como múltiplos componentes da sua aplicação funcionam juntos. Diferentemente dos testes unitários, que isolam funções individuais, os testes de integração testam fluxos reais: requisições HTTP, interações com banco de dados, chamadas a serviços externos simulados. No Laravel, você trabalha com a classe <code>TestCase</code> que oferece helpers poderosos para simular requisições e fazer assertions sobre respostas.</p>
<p>A grande vantagem é que você testa o sistema como um usuário real o usaria. Se sua aplicação falha quando um controller chama um repository que chama um model, o teste de integração detectará. O Laravel carrega todo o container de serviços, executa middlewares, e permite que você valide status codes, conteúdo de resposta, estado do banco de dados e headers — tudo em um único teste coesivo.</p>
<pre><code class="language-php"><?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
class UserRegistrationTest extends TestCase
{
public function test_user_can_register()
{
$response = $this->post('/api/users', [
'name' => 'João Silva',
'email' => 'joao@example.com',
'password' => 'senhaSegura123',
'password_confirmation' => 'senhaSegura123',
]);
$response->assertStatus(201)
->assertJsonStructure(['id', 'name', 'email']);
$this->assertDatabaseHas('users', [
'email' => 'joao@example.com',
]);
}
}</code></pre>
<h2>Feature Tests: A Perspectiva do Usuário</h2>
<p>Feature tests (testes de funcionalidade) são um tipo específico de teste de integração que simulam comportamentos completos do usuário. Você não testa um método isolado — testa um cenário inteiro. "Um usuário faz login, navega para seu dashboard, cria um produto, e recebe uma confirmação." Isso é um feature test.</p>
<p>No Laravel, feature tests herdam de <code>TestCase</code> e estão localizados em <code>tests/Feature</code>. O framework oferece métodos como <code>actingAs()</code> para autenticar usuários, <code>withHeaders()</code> para headers customizados, e métodos fluentes para assertions em JSON e views. A filosofia é testar comportamento observável, não implementação interna.</p>
<pre><code class="language-php"><?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use App\Models\Product;
class ProductManagementTest extends TestCase
{
public function test_authenticated_user_can_create_product()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/api/products', [
'name' => 'Notebook',
'price' => 3500.00,
'description' => 'Notebook de alta performance',
]);
$response->assertStatus(201);
$this->assertDatabaseHas('products', [
'name' => 'Notebook',
'user_id' => $user->id,
]);
}
public function test_unauthenticated_user_cannot_create_product()
{
$response = $this->post('/api/products', [
'name' => 'Notebook',
'price' => 3500.00,
]);
$response->assertStatus(401);
}
public function test_user_can_list_own_products()
{
$user = User::factory()->create();
Product::factory(3)->for($user)->create();
$response = $this->actingAs($user)->get('/api/products');
$response->assertStatus(200)
->assertJsonCount(3, 'data');
}
}</code></pre>
<h2>Estruturando Testes de Integração Efetivos</h2>
<p>A qualidade de seus testes depende de boa estrutura. Use factories para criar dados de teste rapidamente, organize testes em grupos temáticos, e evite testes acoplados (onde um teste depende do resultado de outro). Cada teste deve ser independente e rodar em qualquer ordem.</p>
<p>Leverage do banco de dados em transação: Laravel encapsula cada teste em uma transação que é revertida após execução, mantendo o banco limpo. Use <code>RefreshDatabase</code> no seu <code>TestCase</code> base. Estruture suas assertions para verificar o comportamento mais importante primeiro, depois detalhes. E use factories bem quando quiser dados realistas, mas mock serviços externos que não fazem parte do escopo do teste.</p>
<pre><code class="language-php"><?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use App\Models\Order;
use Illuminate\Foundation\Testing\RefreshDatabase;
class OrderProcessingTest extends TestCase
{
use RefreshDatabase;
public function test_order_is_created_with_correct_status()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/orders', [
'items' => [
['product_id' => 1, 'quantity' => 2],
],
'shipping_address' => '123 Main St',
]);
$response->assertCreated();
$this->assertDatabaseHas('orders', [
'user_id' => $user->id,
'status' => 'pending',
]);
}
public function test_order_with_invalid_product_fails()
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/orders', [
'items' => [
['product_id' => 99999, 'quantity' => 1],
],
]);
$response->assertStatus(422)
->assertJsonValidationErrors('items');
}
}</code></pre>
<h2>Melhores Práticas e Debugging</h2>
<p>Mantenha testes legíveis e bem nomeados: <code>test_authenticated_user_can_delete_own_post_but_not_others_post()</code> deixa claro o que você está testando. Use <code>dump()</code> e <code>dd()</code> durante desenvolvimento para inspecionar respostas JSON. Para testes mais complexos, considere usar Pest ou estruturas BDD (Behavior-Driven Development) que tornam a leitura ainda mais natural.</p>
<p>Moque apenas o que é externo: banco de dados real deve ser testado, mas APIs externas devem ser mockadas com <code>Http::fake()</code>. Teste casos de sucesso, casos de erro, validações, autorização, e cenários edge. Mantenha testes rápidos — se um teste leva mais de alguns segundos, revise o que você está testando. Por fim, rode seus testes frequentemente. Integre com CI/CD para rodá-los automaticamente em cada commit.</p>
<pre><code class="language-php"><?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Support\Facades\Http;
class ExternalAPITest extends TestCase
{
public function test_weather_forecast_is_fetched_correctly()
{
Http::fake([
'api.weather.com/*' => Http::response([
'temperature' => 25,
'condition' => 'sunny',
]),
]);
$user = User::factory()->create();
$response = $this->actingAs($user)
->getJson('/api/weather?city=sao-paulo');
$response->assertOk()
->assertJson([
'temperature' => 25,
'condition' => 'sunny',
]);
}
}</code></pre>
<h2>Conclusão</h2>
<p>Testes de integração e feature tests são pilares de uma aplicação Laravel confiável. Eles validam que seus componentes trabalham em conjunto, capturando bugs que testes unitários perdem. Use <code>RefreshDatabase</code>, organize com factories, e estruture testes independentes. A prática constante com essas ferramentas transforma você em um desenvolvedor que escreve código com confiança — você saberá que suas mudanças funcionam antes de fazer deploy.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://laravel.com/docs/11/testing" target="_blank" rel="noopener noreferrer">Laravel Testing Documentation</a></li>
<li><a href="https://laravel.com/docs/11/eloquent-factories" target="_blank" rel="noopener noreferrer">Laravel Factories</a></li>
<li><a href="https://laravel.com/docs/11/http-tests" target="_blank" rel="noopener noreferrer">HTTP Testing - Laravel Docs</a></li>
<li><a href="https://pestphp.com" target="_blank" rel="noopener noreferrer">Pest PHP - Testing Framework</a></li>
<li><a href="https://www.phptesting.com/" target="_blank" rel="noopener noreferrer">Test Driven Development in PHP</a></li>
</ul>