A test issues an invoice, asserts issued_on equals today(), and fails at midnight or a second later, depending on which side of the assertion read the clock. Nothing about the invoice changed. The clock did.
freezeTime() stops the clock for that test. Carbon::now() and now() return the same instant until the test ends. Call it before the code that writes a timestamp.
01 What it does
Pin the clock, create the invoice, and compare the stored instant to now(). A second call is the same value, so the assertion is exact rather than "within two seconds".
// tests/Feature/IssueInvoiceTest.php
use App\Models\Invoice;
use App\Models\Team;
it('stamps an invoice with the time it was issued', function () {
$this->freezeTime();
$team = Team::factory()->create();
$invoice = Invoice::factory()->for($team)->create([
'issued_on' => now(),
]);
expect($invoice->issued_on->equalTo(now()))->toBeTrue();
});travel() moves the clock and lets it run. travelTo() pins it at a moment you choose, which is what an expiry needs.
$this->freezeTime();
$invoice = Invoice::factory()->create(['due_on' => now()->addDays(30)]);
$this->travel(31)->days();
expect($invoice->due_on->isPast())->toBeTrue();travel() is undone at the end of the test. The next test does not inherit the future.
02 Gotchas
- Freeze before the write. A timestamp taken before
freezeTime()will not equalnow()afterwards. travel()does not freeze. The clock keeps moving after the jump, which is why an expiry test that needs an exact instant wantstravelTo().- A database default of
CURRENT_TIMESTAMPis the database's clock, not Carbon's. Assert a value your code wrote. - Parallel test processes do not share the freeze. Each test freezes its own clock.
03 Related
Freezing time is the lesson. A practical guide to caching Eloquent results is where a TTL test needs the same clock.