Back to Course |
Testing in Laravel 11: Advanced Level

"Fake" Time to the Future/Past

Let's talk about how to fake time in a test. What do I mean by that?

What if you want to test the situation that would happen in the future or should have occurred in the past depending on some, for example, database field like published_at or created_at?

You can set the "fake" time to run a specific test method.


Scenario Example

For example, you have published_at in the database table and want to show only published records on your home page.

Schema::table('products', function (Blueprint $table) {
$table->dateTime('published_at')->nullable()->after('price');
});

Then, in the Model, you have an optional scope of published.

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
 
class Product extends Model
{
use HasFactory;
 
protected $fillable = [
'name',
'price',
'published_at',
];
 
public function scopePublished(Builder $query): Builder
{
return $query->whereNotNull('published_at')
->where('published_at', '<=', now());
}
}

And, in the Controller, you get only the published records.

use App\Models\Product;
use Illuminate\View\View;
 
class ProductController extends Controller
{
public function index(): View
{
$products = Product::published()->get();
 
return view('products.index', compact('products'));
}
 
// ...
}

The Test

Then, in the test, you create a product and set the published_at to some time in the feature.

use App\Models\User;
use App\Models\Product;
use function Pest\Laravel\actingAs;
 
beforeEach(function () {
$this->user = User::factory()->create();
});
 
test('product shows when published at correct time', function () {
$product = Product::factory()->create([
'published_at' => now()->addDay()->setTime(14, 00),
]);
});

Now, what do you need to test? Two scenarios.

First, go to the page and see if the record isn't shown now.

use App\Models\User;
use App\Models\Product;
use function Pest\Laravel\actingAs;
 
beforeEach(function () {
$this->user = User::factory()->create();
});
 
test('product shows when published at correct time', function () {
$product = Product::factory()->create([
'published_at' => now()->addDay()->setTime(14, 00),
]);
 
actingAs($this->user)
->get('/products')
->assertDontSeeText($product->name);
});

Then, you need to fake the time so it is in the future and test if the product appears in the table as published.

We will use $this->travelTo() for that. You can do it within the same test method, or in a separate test method, it's your personal preference.

use App\Models\User;
use App\Models\Product;
use function Pest\Laravel\actingAs;
 
beforeEach(function () {
$this->user = User::factory()->create();
});
 
test('product shows when published at correct time', function () {
$product = Product::factory()->create([
'published_at' => now()->addDay()->setTime(14, 00),
]);
 
actingAs($this->user)
->get('/products')
->assertDontSeeText($product->name);
 
$this->travelTo(now()->addDay()->setTime(14, 01));
 
actingAs($this->user)
->get('/products')
->assertSeeText($product->name);
});

You use $this->travelTo() and provide a Carbon instance with the time in the future. In this case, we added an extra one minute. Then, we assert that we see the text.


Important: Travel BACK in Time

Keep in mind that setting time in a test does NOT automatically reset the time for other tests.

If you want to return to the current time, you can use $this->travelBack().

$this->travelTo(now()->addDay()->setTime(14, 01));
 
actingAs($this->user)
->get('/products')
->assertSeeText($product->name);
 
$this->travelBack();

Alternatively, wrap the travelTo() in a Closure callback function. Everything within a Closure will be executed within the set time, and the "global" time will resume at the current time.

use App\Models\User;
use App\Models\Product;
use function Pest\Laravel\actingAs;
 
beforeEach(function () {
$this->user = User::factory()->create();
});
 
test('product shows when published at correct time', function () {
$product = Product::factory()->create([
'published_at' => now()->addDay()->setTime(14, 00),
]);
 
actingAs($this->user)
->get('/products')
->assertDontSeeText($product->name);
 
$this->travelTo(now()->addDay()->setTime(14, 01), function () use ($product) {
actingAs($this->user)
->get('/products')
->assertSeeText($product->name);
});
});