home / blog / Laravel'de Test Stratejileri: Gerçekten...
Laravel Feb 10, 2026

Laravel'de Test Stratejileri: Gerçekten Hata Yakalayan Testler Yazın

Basit "assert true is true"nun ötesine geçen pratik bir test rehberi — entegrasyon testleri, özellik testleri, test ikilileri ve Laravel'e uygulanan test piramidi.

Laravel'de Test Stratejileri: Gerçekten Hata Yakalayan Testler Yazın

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_something formatinda 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.
tum yazilara don