In a Laravel test, how do you move the clock forward to check that a refund request window closes after 14 days?
answer
- base test case time helpers
- $this->travel(15)->days()
- travelTo(), freezeTime(), travelBack()
- Carbon::setTestNow underneath
- native time() is untouched
basics
~10 sUse the test case's time helpers: $this->travel(15)->days() or $this->travelTo($date) set Carbon's test time, so now() and Eloquent timestamps see the new date. travelBack() or a closure restores real time, and teardown resets it anyway.
solid answer
~40 sLaravel's base test case includes `InteractsWithTime`. `$this->travel(15)->days()` moves the clock forward (negative numbers go back; units from microseconds to years), `$this->travelTo(now()->addDays(15))` jumps to an exact moment, and `$this->freezeTime()` pins the current instant. All of them call `Carbon::setTestNow()`, so `now()`, `Carbon::now()` and Eloquent's `created_at`/`updated_at` see the fake time, and it stays **frozen** there until you move it again. Pass a closure to run code at that time and return to real time afterwards; otherwise call `$this->travelBack()`, though teardown resets Carbon's test time for you. For the refund window: create the order, travel 15 days, assert the refund request is rejected. PHP's own `time()`/`date()` and the database's `NOW()` are not affected.
go deeper
Remember the test case helpers $this->travel(15)->days(), $this->travelTo() and $this->travelBack(), and that now() follows them.
Explain that the helpers set Carbon's test time, that the clock stays frozen, and that teardown resets it.
Test both sides of each time boundary and remove native time() and database NOW() from time-sensitive logic so it stays testable.
Push teams to route all time reads through one clock source so every time-based rule can be tested without waiting.
## Why tests need a controllable clock Business rules like "refunds may be requested within 14 days of delivery" depend on the current time. A test that waits two weeks is impossible, and a test that fakes dates by editing rows is fragile. Laravel's base test case includes the `InteractsWithTime` trait, which moves the application's clock. ## The helpers | Helper | What it does | |---|---| | `$this->travel(15)->days()` | move forward 15 days from now; units run from `microseconds()` to `years()`, and a negative number moves back | | `$this->travelTo($date)` | jump to an exact moment | | `$this->freezeTime()` | pin the current instant and return it | | `$this->freezeSecond()` | pin the start of the current second | | `$this->travelBack()` | return to real time | Each of `travel`, `travelTo`, `freezeTime` and `freezeSecond` accepts an optional **closure**: the closure runs at the chosen time, then real time resumes and the closure's result is returned. ## How it works Every helper ends in `Carbon::setTestNow($date)`. Carbon is the date library behind Laravel's `now()` and `today()` helpers, `Illuminate\Support\Carbon::now()`, and Eloquent's timestamps. Once a test time is set: - `now()` returns that instant, and it **does not advance**. Travelling fixes the clock at the new moment; two calls a second apart return the same value. - `created_at` and `updated_at` written by Eloquent use it. - Code that compares against `now()`, for example `$order->delivered_at->diffInDays(now())`, sees the travelled date. During teardown the base test case calls `Carbon::setTestNow()` with no argument (and the same for `CarbonImmutable`), so a forgotten `travelBack()` does not leak into the next test. ## The refund-window test ```php $order = Order::factory()->delivered()->create(); $this->travel(15)->days(); $this->actingAs($order->customer) ->post("/orders/{$order->id}/refunds", ['amount' => 1500]) ->assertSessionHasErrors('order'); ``` The boundary deserves its own case: travel exactly 14 days and assert the request is still accepted. Tests at the edge are where off-by-one bugs in `>` versus `>=` show up. ## What the clock does not reach 1. **PHP's native functions.** `time()`, `date()` and `new DateTime()` read the system clock and ignore Carbon's test time. Code that uses them cannot be time-travelled; switch it to `now()`. 2. **The database clock.** A query using `NOW()` or a column default of `CURRENT_TIMESTAMP` is evaluated by the database server, not by PHP. 3. **Queued work in other processes.** A real worker has its own clock; in tests, jobs run in-process or are faked, so this rarely bites. ## Choosing a helper - Use **`travel()`** when the rule is relative to something created in the test ("15 days after delivery"). - Use **`travelTo()`** when the rule depends on a calendar moment, such as a refund policy that changes at midnight on the first of the month. - Use **`freezeTime()`** when the test asserts an exact timestamp, for example a `refunded_at` column, since the value it returns is the one the code will write. - Use the **closure form** when only part of a test should run at another time. ## Related helpers - `Sleep::fake()` stops Laravel's `Sleep` class from actually pausing and can advance Carbon's clock with each fake sleep. - Carbon's own `Carbon::setTestNow()` still works directly, but the test-case helpers read better and reset automatically. ## Interview summary - `travel()->days()`, `travelTo()`, `freezeTime()`, `travelBack()`. - Implemented with `Carbon::setTestNow()`; the clock stays frozen at the chosen moment. - Reset automatically at teardown. - Native PHP time functions and database `NOW()` are outside its reach.
- After $this->travelTo($date), two calls to now() one second apart return the same value. Is that a bug?No. `travelTo()` calls `Carbon::setTestNow()`, which freezes Carbon at that instant; time does not flow until you travel again or call `travelBack()`. If a test needs elapsed time, move the clock explicitly with `$this->travel(1)->second()`.
- A refund-window check uses date('Y-m-d') and ignores time travel. What do you change?Native `date()` reads the system clock, which Carbon's test time cannot touch. Rewrite the check with `now()` or `today()`, for example `now()->toDateString()`, so it goes through Carbon and follows `travel()` and `travelTo()` in tests.
Time travel in a Laravel test is like setting every clock in one office to a chosen date and stopping their hands: everyone who checks those clocks agrees on the date, but anyone reading their own wristwatch, PHP's time() or the database's NOW(), still sees today.
saying these in an interview costs you the question
- Time travel also changes PHP's native time() and date() results
- After travel(15)->days() the clock keeps ticking forward normally
- Forgetting travelBack() leaks the fake time into the next test
- travel() changes the database server's NOW() as well