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".

IssueInvoiceTest.phpPHP
// 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.

PHP
$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 equal now() afterwards.
  • travel() does not freeze. The clock keeps moving after the jump, which is why an expiry test that needs an exact instant wants travelTo().
  • A database default of CURRENT_TIMESTAMP is 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.

Freezing time is the lesson. A practical guide to caching Eloquent results is where a TTL test needs the same clock.