Testes no Symfony em 2026: PHPUnit, KernelTestCase e Testes Funcionais
Guia completo para implementar testes em aplicações Symfony em 2026 com PHPUnit, KernelTestCase e testes funcionais. Melhores práticas e exemplos de código.

Os testes automatizados representam o pilar fundamental de qualquer aplicação Symfony robusta e sustentável. Em 2026, o ecossistema de testes do Symfony alcançou uma maturidade excepcional, oferecendo aos desenvolvedores ferramentas poderosas para garantir a qualidade do código. Este guia explora as melhores práticas para implementar testes unitários, de integração e funcionais em projetos Symfony modernos.
Projetos Symfony com cobertura de testes superior a 80% apresentam em média 60% menos bugs em produção. O investimento em testes se paga desde as primeiras iterações do projeto.
Configuração do ambiente de testes
Antes de explorar os diferentes tipos de testes, é necessário configurar corretamente o ambiente. O Symfony 7 simplifica consideravelmente essa etapa graças ao Symfony Flex e às receitas automatizadas.
composer require --dev phpunit/phpunit symfony/test-packO arquivo phpunit.xml.dist na raiz do projeto define a configuração base:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true"
executionOrder="depends,defects"
cacheDirectory=".phpunit.cache">
<php>
<ini name="display_errors" value="1"/>
<ini name="error_reporting" value="-1"/>
<server name="APP_ENV" value="test" force="true"/>
<server name="SHELL_VERBOSITY" value="-1"/>
<server name="KERNEL_CLASS" value="App\Kernel"/>
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>Testes unitários com PHPUnit
Os testes unitários verificam o comportamento isolado de classes e métodos. Eles executam rapidamente e não requerem dependências externas como banco de dados ou container de serviços.
<?php
namespace App\Tests\Unit\Service;
use App\Service\PriceCalculator;
use App\Entity\Product;
use PHPUnit\Framework\TestCase;
class PriceCalculatorTest extends TestCase
{
private PriceCalculator $calculator;
protected function setUp(): void
{
$this->calculator = new PriceCalculator();
}
public function testCalculatePriceWithoutDiscount(): void
{
$product = new Product();
$product->setPrice(100.00);
$result = $this->calculator->calculate($product, quantity: 2);
$this->assertSame(200.00, $result);
}
public function testCalculatePriceWithPercentageDiscount(): void
{
$product = new Product();
$product->setPrice(100.00);
$result = $this->calculator->calculate(
$product,
quantity: 2,
discountPercent: 10
);
$this->assertSame(180.00, $result);
}
/**
* @dataProvider invalidQuantityProvider
*/
public function testCalculateThrowsExceptionForInvalidQuantity(int $quantity): void
{
$this->expectException(\InvalidArgumentException::class);
$product = new Product();
$product->setPrice(100.00);
$this->calculator->calculate($product, quantity: $quantity);
}
public static function invalidQuantityProvider(): array
{
return [
'zero quantity' => [0],
'negative quantity' => [-1],
];
}
}O uso de data providers permite testar múltiplos cenários sem duplicar código de teste. O PHPUnit 11 melhora a sintaxe com atributos nativos do PHP 8.
Testes de integração com KernelTestCase
Os testes de integração verificam a interação entre múltiplos componentes. O KernelTestCase inicializa o kernel do Symfony e fornece acesso ao container de serviços.
<?php
namespace App\Tests\Integration\Service;
use App\Entity\User;
use App\Service\UserRegistrationService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class UserRegistrationServiceTest extends KernelTestCase
{
private UserRegistrationService $registrationService;
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
$container = static::getContainer();
$this->registrationService = $container->get(UserRegistrationService::class);
$this->entityManager = $container->get(EntityManagerInterface::class);
}
public function testRegisterNewUser(): void
{
$user = $this->registrationService->register(
email: 'test@example.com',
password: 'SecurePassword123!',
firstName: 'João',
lastName: 'Silva'
);
$this->assertInstanceOf(User::class, $user);
$this->assertNotNull($user->getId());
$this->assertSame('test@example.com', $user->getEmail());
$this->assertTrue($user->isActive());
}
public function testRegisterDuplicateEmailThrowsException(): void
{
$this->registrationService->register(
email: 'duplicate@example.com',
password: 'SecurePassword123!',
firstName: 'João',
lastName: 'Silva'
);
$this->expectException(\App\Exception\DuplicateEmailException::class);
$this->registrationService->register(
email: 'duplicate@example.com',
password: 'AnotherPassword456!',
firstName: 'Maria',
lastName: 'Santos'
);
}
protected function tearDown(): void
{
parent::tearDown();
$this->entityManager->close();
}
}Isolamento do banco de dados
Para garantir o isolamento dos testes, existem várias estratégias. A mais eficiente utiliza transações com rollback automático:
<?php
namespace App\Tests;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
abstract class DatabaseTestCase extends KernelTestCase
{
protected EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
$this->entityManager = static::getContainer()->get(EntityManagerInterface::class);
$this->entityManager->beginTransaction();
}
protected function tearDown(): void
{
$this->entityManager->rollback();
$this->entityManager->close();
parent::tearDown();
}
}Testes funcionais com WebTestCase
Os testes funcionais simulam requisições HTTP e verificam as respostas completas da aplicação. Eles testam toda a stack, do controller até a view.
<?php
namespace App\Tests\Functional\Controller;
use App\Factory\UserFactory;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;
class ProductControllerTest extends WebTestCase
{
use Factories;
use ResetDatabase;
public function testListProductsReturnsSuccessResponse(): void
{
$client = static::createClient();
$client->request('GET', '/products');
$this->assertResponseIsSuccessful();
$this->assertSelectorExists('h1');
}
public function testShowProductDisplaysCorrectInformation(): void
{
$client = static::createClient();
$client->request('GET', '/products/symfony-book');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Symfony Book');
$this->assertSelectorExists('.product-price');
}
public function testCreateProductRequiresAuthentication(): void
{
$client = static::createClient();
$client->request('GET', '/admin/products/new');
$this->assertResponseRedirects('/login');
}
public function testAuthenticatedUserCanCreateProduct(): void
{
$client = static::createClient();
$user = UserFactory::createOne(['roles' => ['ROLE_ADMIN']]);
$client->loginUser($user->_real());
$client->request('GET', '/admin/products/new');
$this->assertResponseIsSuccessful();
$client->submitForm('Criar', [
'product[name]' => 'Novo Produto',
'product[price]' => '49.99',
'product[description]' => 'Descrição do produto',
]);
$this->assertResponseRedirects('/admin/products');
}
}Testes de APIs JSON
As aplicações modernas frequentemente expõem APIs REST ou GraphQL. O Symfony facilita os testes com asserções dedicadas:
<?php
namespace App\Tests\Functional\Api;
use App\Factory\ProductFactory;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;
class ProductApiTest extends WebTestCase
{
use Factories;
use ResetDatabase;
public function testGetProductsReturnsJsonResponse(): void
{
ProductFactory::createMany(3);
$client = static::createClient();
$client->request('GET', '/api/products', [], [], [
'HTTP_ACCEPT' => 'application/json',
]);
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('Content-Type', 'application/json');
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertCount(3, $data['products']);
}
public function testCreateProductWithValidData(): void
{
$client = static::createClient();
$client->request('POST', '/api/products', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([
'name' => 'API Product',
'price' => 29.99,
]));
$this->assertResponseStatusCodeSame(201);
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertArrayHasKey('id', $data);
$this->assertSame('API Product', $data['name']);
}
public function testCreateProductWithInvalidDataReturnsBadRequest(): void
{
$client = static::createClient();
$client->request('POST', '/api/products', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([
'name' => '',
'price' => -10,
]));
$this->assertResponseStatusCodeSame(400);
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertArrayHasKey('errors', $data);
}
}Mocking e dublês de teste
Para isolar o código de dependências externas, os mocks são indispensáveis:
<?php
namespace App\Tests\Unit\Service;
use App\Service\NotificationService;
use App\Service\PaymentGateway;
use App\Service\OrderProcessor;
use App\Entity\Order;
use PHPUnit\Framework\TestCase;
class OrderProcessorTest extends TestCase
{
public function testProcessOrderCallsPaymentGateway(): void
{
$paymentGateway = $this->createMock(PaymentGateway::class);
$notificationService = $this->createMock(NotificationService::class);
$paymentGateway
->expects($this->once())
->method('charge')
->with(
$this->equalTo(99.99),
$this->isType('string')
)
->willReturn(true);
$notificationService
->expects($this->once())
->method('sendOrderConfirmation');
$processor = new OrderProcessor($paymentGateway, $notificationService);
$order = new Order();
$order->setTotal(99.99);
$result = $processor->process($order);
$this->assertTrue($result);
}
}Pronto para mandar bem nas entrevistas de Symfony?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Organização dos testes e melhores práticas
Uma estrutura de testes clara facilita a manutenção a longo prazo:
tests/
├── Unit/
│ ├── Entity/
│ ├── Service/
│ └── Util/
├── Integration/
│ ├── Repository/
│ └── Service/
├── Functional/
│ ├── Controller/
│ └── Api/
└── bootstrap.phpOs testes devem seguir o padrão AAA (Arrange, Act, Assert) e cada teste deve verificar apenas um comportamento. Nomes de métodos explícitos documentam automaticamente o comportamento esperado.
Execução e integração contínua
O PHPUnit oferece diversas opções para executar testes de forma específica:
# Executar todos os testes
./vendor/bin/phpunit
# Executar um arquivo específico
./vendor/bin/phpunit tests/Unit/Service/PriceCalculatorTest.php
# Executar testes com cobertura de código
./vendor/bin/phpunit --coverage-html coverage/
# Executar apenas testes rápidos
./vendor/bin/phpunit --testsuite=UnitA integração com GitHub Actions ou GitLab CI automatiza a execução dos testes a cada push:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: password
POSTGRES_DB: test_db
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: xdebug
- run: composer install
- run: ./vendor/bin/phpunit --coverage-clover coverage.xmlConclusão
Os testes constituem um investimento essencial para qualquer projeto Symfony profissional. A combinação de testes unitários, de integração e funcionais oferece uma cobertura completa que assegura as evoluções do código. O PHPUnit 11, junto com as ferramentas do Symfony como KernelTestCase e WebTestCase, fornece um ambiente de testes maduro e eficiente.
A adoção progressiva de testes, começando pelas partes críticas da aplicação, permite construir uma base sólida. As equipes que investem em testes automatizados observam uma redução significativa de regressões e maior confiança durante os deploys.
Compartilhar
Artigos relacionados

Injeção de dependências do Symfony: contêiner de serviços, autowiring e tags em 2026
Mergulho na injeção de dependências do Symfony: contêiner de serviços, autowiring, atributo #[Autowire], ligação de interfaces, serviços com tags com #[AutowireIterator], compiler passes e serviços preguiçosos no Symfony 7.4 e 8.0.

Symfony e Docker em 2026: ambiente de desenvolvimento e deploy em produção
Um fluxo completo de Symfony com Docker para 2026: ambiente de desenvolvimento com FrankenPHP, imagem de produção multi-stage, modo worker, segredos criptografados, otimização de imagem e deploys sem downtime com migrações.

Segurança no Symfony em 2026: Voters, Firewalls e Perguntas de Entrevista Técnica
Análise aprofundada da segurança no Symfony cobrindo voters, firewalls, access tokens e autenticação, com as perguntas de entrevista técnica mais frequentes para desenvolvedores Symfony.