The PDF test kept the file off the real disk. A test that asserts issued_on, sent_at or an expiry will fail when it runs a second later than the code, or across midnight. freezeTime() makes now() return one instant for the whole test.
Test-driven Laravel · Lesson 3
Freezing time
Video
01 Pin the clock, then move it when the date matters
Freeze at the start, before the code writes a timestamp. travel() is for the case where an hour has to pass.
it('expires the payment link after an hour', function () {
$this->freezeTime();
$invoice = Invoice::factory()->issued()->create();
$this->actingAs($invoice->team->owner)
->post('/invoices/'.$invoice->id.'/link');
$this->travel(61)->minutes();
$this->get('/pay/'.$invoice->fresh()->pay_token)
->assertForbidden();
});The snippet on freezing time shows freezeTime(), travel() and travelTo() on their own, with a password reset token.
Call travel() only in the test that needs the clock to move. A frozen clock in every test hides a bug that depends on time passing.
02 Assert a timestamp only after the clock is pinned
sent_at is written with now(). Without freezeTime(), the assertion has to allow a range, and a range hides a bug that writes yesterday. Freeze, act, then compare the column to the frozen instant.
it('records when the invoice was sent', function () {
$this->freezeTime();
$invoice = Invoice::factory()->issued()->create();
$this->actingAs($invoice->team->owner)
->post('/invoices/'.$invoice->id.'/send')
->assertRedirect();
expect($invoice->fresh()->sent_at->equalTo(now()))->toBeTrue();
});travel() comes after that post when the test is about expiry, as in the link above. It does not belong in this one. The sent_at test wants a single instant, and now() inside the expectation is that same instant because the clock is still frozen.
freezeTime() also freezes Carbon inside the factory. An issued_on of now() in the factory is the same instant as the assertion. If the test needs a date in the past, pass that date in the factory and leave the clock frozen. travel() before the post would make sent_at match the travelled time, and the test would still pass for the wrong reason.
Recap
You can pin now() for one test, and move it only when the behaviour is an expiry.
- Freeze before the write.
travel()is for the test where an hour has to pass. It does not belong on asent_atequality.- A frozen clock in every test hides a bug that depends on time passing.
Check yourself · 3 questions
Question 1 of 3sent_at is compared to now() without freezeTime(). The test flakes. What do you add?