Bir yıl kadar önce, Laravel’in geçici depolama URL’lerini test ederken zamanın dondurulması hakkında bir makale yazmıştım.
Ne oldu biliyor musunuz? Aynı soruna bir kez daha, ama farklı bir açıdan rastladım. Bu, testlerimizde zamanı kontrol etmenin sadece bir iyi niyet durumu değil, zorunlu olduğunu hatırlattı.
Sorun
Testim bilgisayarımdaki gibi düzgün çalışıyordu. Ancak, CI ortamında rastgele olarak başarısız oldu:
public function test_order_item_cancel(): void
{
$user = UserFixture::createUser();
$this->actingAsFrontendUser($user);
$order = OrderFixture::create($user);
$orderItem = OrderItemFactory::new()->for($order)->for($user)->create();
$response = $this->put(route('api-v2:order.order-items.cancel', ['uuid' => $orderItem->uuid]));
$response->assertNoContent();
$this->assertDatabaseHas(OrderItem::class, [
'uuid' => $orderItem->uuid,
'canceled_at' => Date::now(),
]);
}
Bazen bu hatayı alıyordum:
Failed asserting that a row in the table [order_items] matches the attributes {
"canceled_at": "2026-01-09T10:24:52.008406Z"
}.
Found: [
{
"canceled_at": "2026-01-09 12:24:51"
}
].
Başlangıçta sadece omuz silkip “yeniden denemek” yaptım, herkesin yaptığı gibi. 😅 Ama The Flaky Test Chronicles VI makalesini okuduktan sonra, dikkat etmem gerektiğini fark ettim. Bu gerçek bir hata mıydı yoksa sadece dalgalı bir test mi?
Neden Bu Oluyor?
Problemin oldukça basit: Date::now() iki kez çağrılıyor, ama aynı anda değil.
İlk olarak, kontrolör canceled_at‘i ayarladığı zaman. İkincisi ise, test değeri kontrol ettiğinde.
Hatta çok küçük bir gecikme, belki sadece milisaniye, bu iki zaman damgasını farklı kılabilir. CI genellikle daha yavaş çalıştığı için bu, orada daha sık yaşanır.
Çözüm
Yalnızca isteği yapmadan önce zamanı dondurun:
// Seçenek 1
$this->freezeTime();
// Seçenek 2
$now = Date::now();
Date::setTestNow($now);
$response = $this->put(route('api-v2:order.order-items.cancel', ['uuid' => $orderItem->uuid]));
$this->assertDatabaseHas(OrderItem::class, [
'uuid' => $orderItem->uuid,
'canceled_at' => $now,
]);
$this->freezeTime() sadece test yaşam döngüsüne özel bir Date::setTestNow() sarmalayıcısıdır.
Artık hem kontrolör hem de test aynı zaman damgasını paylaşıyor. Artık eşleşme sorunu yok.
Başka Bir Yöntem
Eğer tam zaman damgasıyla ilgilenmiyorsanız ve sadece alanın boş olmadığından emin olmak istiyorsanız, şu şekilde devam edebilirsiniz:
$this->assertDatabaseMissing(OrderItem::class, [
'uuid' => $orderItem->uuid,
'canceled_at' => null,
]);
Son Düşünceler
Eğer testleriniz zamana bağlıysa, ona hakim olun. Testiniz lokal ortamda başarılı olup CI’da başarısız olduğunda, Date::setTestNow() veya $this->freezeTime() kullanarak zamanı dondurun. Testlerinizi güvenilir hale getirin, neyi test ettiğinizi kontrol edin. Doğru yapın. Deterministik tutun. Testlerinize güvenin.
Yazarın Notu
Vakit ayırdığınız için teşekkürler!
Beni dev.to, LinkedIn üzerinden takip edebilir veya GitHub üzerindeki işlerime göz atabilirsiniz.
Gerçek dünyadan Laravel notları.
Kaynak: Orijinal Makale


