Symfony'de Test 2026: PHPUnit, KernelTestCase ve Fonksiyonel Testler
PHPUnit 12, KernelTestCase ve WebTestCase ile Symfony test stratejileri. Birim, entegrasyon ve fonksiyonel testler icin kapsamli kilavuz.

PHPUnit 12 ile Symfony uygulamalarini test etmek, birim testlerinden tam HTTP istek simulasyonlarina kadar uygulama davranisini dogrulama konusunda yapilandirilmis bir yaklasim sunar. Framework'un test altyapisi servis konteyneri ile sikica entegre olur ve bu sayede servisler, controller'lar ve veritabani islemlerinin minimum tekrarlayan kodla test edilmesi kolaylasir.
Symfony 7.3, PHPUnit 12 destegi, ResetInterface ile gelistirilmis test izolasyonu ve fonksiyonel test assertion'larini daha okunakli hale getiren CrawlerSelector bileseni ile birlikte gelir. WebTestCase artik HTTP/2 push assertion'larini kutusundan cikar cikmaz destekler.
PHPUnit 12 ile Servis Birim Testleri
Birim testleri, tek tek siniflari izole bir sekilde dogrular. Konteyner'a bagimliligi olmayan Symfony servisleri icin standart PHPUnit testleri herhangi bir framework katilimi olmadan calisir.
namespace App\Tests\Unit\Service;
use App\Service\PriceCalculator;
use PHPUnit\Framework\TestCase;
final class PriceCalculatorTest extends TestCase
{
private PriceCalculator $calculator;
protected function setUp(): void
{
// Create instance directly - no container needed
$this->calculator = new PriceCalculator(vatRate: 0.20);
}
public function testCalculateTotalWithVat(): void
{
$result = $this->calculator->calculateTotal(100.00);
// Assert exact decimal value with delta for float comparison
$this->assertEqualsWithDelta(120.00, $result, 0.001);
}
public function testCalculateTotalWithZeroAmount(): void
{
$result = $this->calculator->calculateTotal(0.00);
$this->assertEqualsWithDelta(0.00, $result, 0.001);
}
}Saf birim testleri entegrasyon testlerinden daha hizli calisir ve hatalari tam olarak belirler. Bu testler, veri donusturmesi yapan veya hesaplamalar gerceklestiren is mantigi siniflari icin kullanilmalidir.
KernelTestCase ile Entegrasyon Testleri
Servisler Symfony konteynerine bagimli oldugunda—veritabani baglantilari, cache adapterleri veya diger servisler—KernelTestCase minimal bir kernel baslatir ve servis konteynerine erisim saglar.
namespace App\Tests\Integration\Repository;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
final class UserRepositoryTest extends KernelTestCase
{
private EntityManagerInterface $em;
private UserRepository $repository;
protected function setUp(): void
{
// Boot kernel and fetch services from container
self::bootKernel();
$container = static::getContainer();
$this->em = $container->get(EntityManagerInterface::class);
$this->repository = $container->get(UserRepository::class);
}
public function testFindByEmailReturnsUserWhenExists(): void
{
// Arrange: create and persist test user
$user = new User();
$user->setEmail('test@example.com');
$user->setPassword('hashed_password');
$this->em->persist($user);
$this->em->flush();
// Act: query through repository
$found = $this->repository->findByEmail('test@example.com');
// Assert: verify retrieval
$this->assertNotNull($found);
$this->assertSame('test@example.com', $found->getEmail());
}
protected function tearDown(): void
{
// Clean up to prevent test pollution
$this->em->createQuery('DELETE FROM App\Entity\User')->execute();
parent::tearDown();
}
}getContainer() metodu, test amacli olarak ozel servisleri acan test konteynerini dondurur. Bu, ozel servislerin erisime kapali kaldigi calisma zamani konteynerinden farklidir.
Transaction'larla Veritabani Izolasyonu
Test izolasyonu, bir testteki verilerin digerini etkilemesini onler. DAMADoctrineTestBundle her testi otomatik olarak geri alinan bir transaction icine sarar.
namespace App\Tests\Integration\Service;
use App\Entity\Order;
use App\Service\OrderService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
final class OrderServiceTest extends KernelTestCase
{
private OrderService $orderService;
private EntityManagerInterface $em;
protected function setUp(): void
{
self::bootKernel();
$container = static::getContainer();
$this->orderService = $container->get(OrderService::class);
$this->em = $container->get(EntityManagerInterface::class);
}
public function testCreateOrderPersistsToDatabase(): void
{
// With DAMA bundle, this persists within a transaction
$order = $this->orderService->create(
customerId: 1,
items: [['sku' => 'ABC123', 'quantity' => 2]]
);
// Verify persistence
$this->em->clear();
$persisted = $this->em->find(Order::class, $order->getId());
$this->assertNotNull($persisted);
$this->assertCount(1, $persisted->getItems());
}
// Transaction rolls back after test - no cleanup needed
}Bundle, phpunit.xml.dist dosyasina extension eklenerek yapilandirilir. Boylece her test, manuel temizleme kodu olmadan tam izolasyonda calisir.
Symfony mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
WebTestCase ile Fonksiyonel Testler
Fonksiyonel testler HTTP isteklerini simule eder ve yanitlari dogrular. WebTestCase, web sunucusu baslatmadan controller'lara istek yapan bir istemci saglar.
namespace App\Tests\Functional\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\HttpFoundation\Response;
final class ProductControllerTest extends WebTestCase
{
public function testListProductsReturnsJsonArray(): void
{
// Create client and make GET request
$client = static::createClient();
$client->request('GET', '/api/products');
// Assert response status
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
// Assert JSON structure
$this->assertResponseHeaderSame('Content-Type', 'application/json');
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($data);
$this->assertArrayHasKey('products', $data);
}
public function testCreateProductRequiresAuthentication(): void
{
$client = static::createClient();
$client->request('POST', '/api/products', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['name' => 'New Product', 'price' => 29.99]));
// Unauthenticated requests should return 401
$this->assertResponseStatusCodeSame(Response::HTTP_UNAUTHORIZED);
}
}$client->request() metodu HTTP metodu, URI, parametreler, dosyalar, sunucu degiskenleri ve body icerigini kabul eder. Symfony istegi middleware, guvenlik ve routing dahil tam kernel uzerinden isler.
Kimlik Dogrulanmis Istekleri Test Etme
Kimlik dogrulama gerektiren endpoint'ler icin Symfony'nin guvenlik bileseni test istemcisinde loginUser() metodunu saglar.
namespace App\Tests\Functional\Controller;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class AdminControllerTest extends WebTestCase
{
public function testAdminDashboardRequiresAdminRole(): void
{
$client = static::createClient();
$container = static::getContainer();
// Fetch a test user with ROLE_ADMIN
$userRepository = $container->get(UserRepository::class);
$adminUser = $userRepository->findOneBy(['email' => 'admin@example.com']);
// Authenticate the client
$client->loginUser($adminUser);
// Access protected route
$client->request('GET', '/admin/dashboard');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Admin Dashboard');
}
public function testRegularUserCannotAccessAdmin(): void
{
$client = static::createClient();
$container = static::getContainer();
$userRepository = $container->get(UserRepository::class);
$regularUser = $userRepository->findOneBy(['email' => 'user@example.com']);
$client->loginUser($regularUser);
$client->request('GET', '/admin/dashboard');
// Should be forbidden, not redirected to login
$this->assertResponseStatusCodeSame(403);
}
}loginUser() metodu, giris formundan gecmeden guvenlik token'ini ayarlar, bu da testleri daha hizli ve odakli hale getirir.
Form Testi ve CSRF Yonetimi
Form testi CSRF token'larinin islenmesini gerektirir. Crawler bileseni form elemanlarini cikarir ve uygun token yonetimiyle gonderir.
namespace App\Tests\Functional\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
final class RegistrationControllerTest extends WebTestCase
{
public function testRegistrationFormSubmission(): void
{
$client = static::createClient();
// Load the registration page
$crawler = $client->request('GET', '/register');
// Extract the form and fill fields
$form = $crawler->selectButton('Register')->form([
'registration_form[email]' => 'newuser@example.com',
'registration_form[plainPassword]' => 'SecurePass123!',
'registration_form[agreeTerms]' => true,
]);
// Submit - CSRF token is included automatically
$client->submit($form);
// Assert redirect after successful registration
$this->assertResponseRedirects('/login');
// Follow redirect and verify flash message
$client->followRedirect();
$this->assertSelectorTextContains('.alert-success', 'Registration successful');
}
public function testRegistrationValidationErrors(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/register');
// Submit with invalid email
$form = $crawler->selectButton('Register')->form([
'registration_form[email]' => 'not-an-email',
'registration_form[plainPassword]' => '123', // Too short
'registration_form[agreeTerms]' => false,
]);
$client->submit($form);
// Should stay on same page with errors
$this->assertResponseIsSuccessful();
$this->assertSelectorExists('.invalid-feedback');
}
}selectButton() metodu formu submit buton metnine gore bulur. Gorunur butonu olmayan formlar icin CSS seciici ile selectForm() kullanilabilir.
Konsol Komutlarini Test Etme
Komutlar genellikle veri aktarimlari veya zamanlanmis gorevler gibi kritik islemleri gerceklestirir. CommandTester komutlari calistirir ve ciktilarini yakalar.
namespace App\Tests\Command;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Console\Tester\CommandTester;
final class ImportUsersCommandTest extends KernelTestCase
{
public function testImportUsersWithValidCsv(): void
{
self::bootKernel();
$application = new Application(self::$kernel);
$command = $application->find('app:import-users');
$tester = new CommandTester($command);
// Execute with arguments and options
$tester->execute([
'file' => 'tests/fixtures/users.csv',
'--dry-run' => true,
]);
// Assert exit code (0 = success)
$this->assertSame(0, $tester->getStatusCode());
// Assert output contains expected text
$output = $tester->getDisplay();
$this->assertStringContainsString('Imported 5 users', $output);
}
public function testImportUsersFailsWithMissingFile(): void
{
self::bootKernel();
$application = new Application(self::$kernel);
$command = $application->find('app:import-users');
$tester = new CommandTester($command);
$tester->execute(['file' => 'nonexistent.csv']);
// Non-zero exit code indicates failure
$this->assertSame(1, $tester->getStatusCode());
$this->assertStringContainsString('File not found', $tester->getDisplay());
}
}Etkilesimli girisi olan komutlar icin execute() metoduna ikinci arguman olarak ['interactive' => false] gecirilebilir veya kullanici yanitlarini simule etmek icin setInputs() kullanilabilir.
Asenkron Gorevler icin Messenger Testi
Symfony Messenger handler'larini test etmek, mesajlarin dogru sekilde gonderildigini ve handler'larin beklenildigi gibi islem yaptigini dogrulamayi gerektirir.
namespace App\Tests\Integration\Message;
use App\Message\SendWelcomeEmail;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Transport\InMemoryTransport;
final class SendWelcomeEmailHandlerTest extends KernelTestCase
{
public function testWelcomeEmailIsDispatched(): void
{
self::bootKernel();
$container = static::getContainer();
$bus = $container->get(MessageBusInterface::class);
// Dispatch the message
$bus->dispatch(new SendWelcomeEmail(userId: 123));
// Retrieve the in-memory transport configured in config/packages/test/messenger.yaml
/** @var InMemoryTransport $transport */
$transport = $container->get('messenger.transport.async');
// Assert message was sent
$this->assertCount(1, $transport->getSent());
$envelope = $transport->getSent()[0];
$message = $envelope->getMessage();
$this->assertInstanceOf(SendWelcomeEmail::class, $message);
$this->assertSame(123, $message->getUserId());
}
}Gonderilen mesajlari gercek kuyruk islemesi olmadan yakalamak icin config/packages/test/messenger.yaml dosyasinda bir in-memory transport yapilandirilmalidir.
Kod Kapsami Yapilandirmasi
PHPUnit kod kapsami raporlari test edilmemis kod yollarini belirler. Kapsam yapilandirmasi phpunit.xml.dist dosyasinda yapilir.
<!-- phpunit.xml.dist -->
<?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"
cacheResult="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
<testsuite name="Functional">
<directory>tests/Functional</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>src/DataFixtures</directory>
<directory>src/Migrations</directory>
</exclude>
</source>
<coverage>
<report>
<html outputDirectory="var/coverage"/>
<clover outputFile="var/coverage/clover.xml"/>
</report>
</coverage>
</phpunit>Testler php bin/phpunit --coverage-html var/coverage komutuyla kapsamla birlikte calistirilir. HTML raporu her satirin kapsam durumunu gosterir.
Sonuc
- Konteyner bagimliligi olmayan saf birim testleri icin
TestCasekullanilir - Testler konteynerden servis gerektirdiginde
KernelTestCasekullanilir - HTTP istek simulasyonu ve yanit assertion'lari icin
WebTestCasekullanilir - Testler arasinda otomatik transaction rollback icin DAMADoctrineTestBundle yapilandirilir
- Giris formlarini simule etmek yerine
loginUser()ile kimlik dogrulama test edilir - Giris/cikis yakalama ile konsol komutu dogrulamasi icin
CommandTesterkullanilir - Messenger testi icin test ortaminda in-memory transport yapilandirilir
- Testler netlik icin Unit, Integration ve Functional dizinlerine duzenlenir
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Paylaş
İlgili makaleler

Symfony Bağımlılık Enjeksiyonu: Servis Konteyneri, Autowiring ve Etiketler 2026
Symfony bağımlılık enjeksiyonuna derinlemesine bakış: servis konteyneri, autowiring, #[Autowire] özniteliği, arayüz bağlama, #[AutowireIterator] ile etiketlenmiş servisler, compiler pass'ler ve Symfony 7.4 ile 8.0'da tembel servisler.

2026'da Symfony ve Docker: Geliştirme Ortamı ve Üretim Dağıtımı
2026 için eksiksiz bir Symfony Docker iş akışı: FrankenPHP geliştirme ortamı, çok aşamalı üretim imajı, worker modu, şifreli sırlar, imaj optimizasyonu ve migration'lı kesintisiz dağıtımlar.

2026'da Symfony Guvenlik Sistemi: Voter'lar, Firewall'lar ve Teknik Mulakat Sorulari
Symfony guvenlik sistemine kapsamli bakis: firewall'lar, voter'lar, IsGranted ozelligi, karar stratejileri, Twig 7.4 hata ayiklama ve PHP gelistiricileri icin teknik mulakat sorulari.