Laravel'de Test Piramidi
Test piramidi basittir: altta cok sayida hizli unit test, ortada daha az integration test ve en ustte bir avuc uctan uca test. Ancak Laravel'de bu katmanlar arasindaki sinirlar bulaniklasir — ve aslinda bu gayet normaldir.
Laravel'in feature test'leri (sahte bir HTTP istegi ile route'larinizi test eden) en ideal noktadir. Controller'larinizi, middleware'lerinizi, dogrulama kurallarinizi, yetkilendirmenizi, veritabani sorgularinizi ve is mantiginizi hep birlikte test ederler. Her commit'te calistirilabilecek kadar hizli ve gercek hatalari yakalayabilecek kadar kapsamlidirlar.
Feature Test'ler: Birincil Test Stratejiniz
class CreateOrderTest extends TestCase
{
use RefreshDatabase;
public function test_authenticated_user_can_create_an_order(): void
{
$user = User::factory()->create();
$product = Product::factory()->create(['price' => 2999, 'stock' => 10]);
$response = $this->actingAs($user)
->postJson('/api/orders', [
'items' => [
['product_id' => $product->id, 'quantity' => 2],
],
'shipping_address' => '123 Main St',
]);
$response
->assertStatus(201)
->assertJsonStructure([
'data' => ['id', 'total', 'status', 'items'],
])
->assertJsonPath('data.total', 5998)
->assertJsonPath('data.status', 'pending');
$this->assertDatabaseHas('orders', [
'user_id' => $user->id,
'total' => 5998,
]);
$this->assertDatabaseHas('order_items', [
'product_id' => $product->id,
'quantity' => 2,
]);
// Verify stock was decremented
$this->assertEquals(8, $product->fresh()->stock);
}
public function test_guest_cannot_create_an_order(): void
{
$response = $this->postJson('/api/orders', [
'items' => [['product_id' => 1, 'quantity' => 1]],
]);
$response->assertStatus(401);
}
public function test_validation_rejects_empty_items(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/orders', [
'items' => [],
]);
$response
->assertStatus(422)
->assertJsonValidationErrors(['items']);
}
public function test_cannot_order_more_than_available_stock(): void
{
$user = User::factory()->create();
$product = Product::factory()->create(['stock' => 2]);
$response = $this->actingAs($user)
->postJson('/api/orders', [
'items' => [
['product_id' => $product->id, 'quantity' => 5],
],
]);
$response->assertStatus(422);
$this->assertDatabaseCount('orders', 0);
}
}
Harici Servisleri Test Etme
Testlerde asla gercek harici API'leri cagirmayin. Fake'ler, mock'lar veya Laravel'in yerlesik fake mekanizmalarini kullanin:
class PaymentProcessingTest extends TestCase
{
use RefreshDatabase;
public function test_successful_payment_creates_transaction(): void
{
// Arrange: fake the payment gateway
$this->mock(PaymentGateway::class, function ($mock) {
$mock->shouldReceive('charge')
->once()
->with(
Mockery::on(fn (Money $m) => $m->inCents() === 5000),
Mockery::type(PaymentMethod::class)
)
->andReturn(PaymentResult::success('txn_abc123', new Money(5000)));
});
$user = User::factory()->create();
$order = Order::factory()->for($user)->create(['total' => 5000]);
// Act
$response = $this->actingAs($user)
->postJson("/api/orders/{$order->id}/pay", [
'payment_method' => 'pm_test_visa',
]);
// Assert
$response->assertStatus(200);
$this->assertEquals('paid', $order->fresh()->status);
}
}
// Testing email sending
class WelcomeEmailTest extends TestCase
{
public function test_welcome_email_is_sent_on_registration(): void
{
Mail::fake();
$this->postJson('/api/register', [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'password123',
'password_confirmation' => 'password123',
]);
Mail::assertSent(WelcomeEmail::class, function ($mail) {
return $mail->hasTo('john@example.com');
});
}
}
// Testing queued jobs
class OrderProcessingTest extends TestCase
{
public function test_order_creation_dispatches_sync_job(): void
{
Queue::fake();
// ... create order
Queue::assertPushed(SyncOrderToErp::class, function ($job) use ($order) {
return $job->orderId === $order->id;
});
}
}
Veritabani Test Stratejileri
// RefreshDatabase: migrates once, wraps each test in a transaction
// Best for most test suites
class MyTest extends TestCase
{
use RefreshDatabase;
}
// Factories: generate realistic test data
class UserFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'password' => Hash::make('password'),
];
}
// State methods for specific scenarios
public function admin(): self
{
return $this->state(['role' => 'admin']);
}
public function suspended(): self
{
return $this->state(['suspended_at' => now()]);
}
}
// Usage in tests
$admin = User::factory()->admin()->create();
$suspended = User::factory()->suspended()->create();
$users = User::factory()->count(50)->create();
Yetkilendirme Testleri
class AdminAccessTest extends TestCase
{
use RefreshDatabase;
public function test_admin_can_delete_users(): void
{
$admin = User::factory()->admin()->create();
$user = User::factory()->create();
$response = $this->actingAs($admin)
->deleteJson("/api/users/{$user->id}");
$response->assertStatus(204);
$this->assertSoftDeleted('users', ['id' => $user->id]);
}
public function test_regular_user_cannot_delete_users(): void
{
$user = User::factory()->create();
$otherUser = User::factory()->create();
$response = $this->actingAs($user)
->deleteJson("/api/users/{$otherUser->id}");
$response->assertStatus(403);
$this->assertDatabaseHas('users', ['id' => $otherUser->id]);
}
}
Ne Test Edilmeli (Ve Ne Atlanmali)
- Her zaman test edin: Kritik is mantigi, yetkilendirme kurallari, dogrulama kurallari, hesaplamalardaki uc durumlar, API sozlesmeleri
- Genellikle test edin: Karmasik veritabani sorgulari, job/event dispatch islemleri, bildirim gonderimi
- Atlayin: Framework'un dahili mekanizmalari, basit getter/setter'lar, Eloquent iliskisel tanimlari, framework'un garanti ettigi acik kodlar
Testleri Surdurulebilir Kilma
Implementasyonu degil, davranisi tanimlayan testler yazin. Ic yapida refactoring yaptiginizda davranis degismemesine ragmen testleriniz bozuluyorsa, testleriniz implementasyona fazla bagli demektir.
- Testleri
test_user_can_do_somethingformatinda adlandirin — dokumantasyon gibi okunurlar - AAA kalibini kullanin: Arrange, Act, Assert — acikca ayrilmis sekilde
- Private metodlari dogrudan test etmeyin — onlari public API uzerinden test edin
- Her testi bagimsiz tutun — hicbir test baska bir testin durumuna bagli olmamalidir
- Testlerinizi her push'ta CI uzerinde calistirin. Duzenli olarak calistirilmayan testler eskir.