Time travel after each Laravel Factory

Perform a time travel after every Factory when testing your Laravel application.

Created on July 2021

Time travel after each Laravel Factory

If you ever tested an endpoint returning a collection of data and, for example, by default you sort them by created_at, you may have been struggling to check the correct order of the result. If you either created multiple data with User::factory()->count(3)->create() or just a few User::factory()->create() one after another, their timestamps will be pretty much the same, providing wrong results because the sorting will be wrong.

You have two solutions to solve this problem: the first one (ok, but not reusable) is to travel in time after each factory using Laravel's time testing helpers:

$user = User::factory()->create();

$this->travel(1)->minutes();

$user2 = User::factory()->create();

$this->travel(1)->minutes();

$user3 = User::factory()->create();

dump($user1->created_at); // 2021-01-01 00:00:00
dump($user2->created_at); // 2021-01-01 00:01:00
dump($user3->created_at); // 2021-01-01 00:02:00

$this->getJson('/users')
	->assertOk()
    ->assertJson(...);

Although this just works fine, it can be annoying when you need this logic in multiple tests. A smartest solution (recommended) is to use the Factory's callbacks with a bit of Carbon testing magic:

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;

class UserFactory extends Factory
{
	/**
     * The name of the factory's corresponding model.
     *
     * @var string
	 */
    protected $model = User::class;

	/**
     * Configure the model factory.
     *
     * @return $this
	 */
    public function configure()
    {
        return $this->afterCreating(function (User $user) {
            Carbon::setTestNow(Carbon::now()->addMinute());
        });
    }
}

What we are doing here is telling Carbon to set the current time to "+1 minute" after each Factory creation. Then, in our test, we can get rid of those time travels:

$user = User::factory()->create();
$user2 = User::factory()->create();
$user3 = User::factory()->create();

dump($user1->created_at); // 2021-01-01 00:00:00
dump($user2->created_at); // 2021-01-01 00:01:00
dump($user3->created_at); // 2021-01-01 00:02:00

// Or...

$users = User::factory()->count(3)->create();

foreach ($users as $user) {
	dump($user->created_at);
}

// 2021-01-01 00:00:00
// 2021-01-01 00:01:00
// 2021-01-01 00:02:00

Next

Add elapsed and estimated time to Laravel (and Symfony) console commands

Previous

Laravel FIFO queue with Redis