Using Carbon::setTestNow() to make time sensitive assertions in Laravel and PHPUnit
Published: 24.01.22 Last Updated: 25.01.22
In some tests, assertions on time can become a problem, let's take a look at the following examples and see how Carbon::setTestNow()
can solve potential time issues in assertions showing up.
Example 1: A file is being saved with a date-time stamp appended in the filename
// BEFORE
/** @test */
public function a_spreadsheet_is_exported_to_s3()
{
Order::factory()->count(3)->create();
/*
* Let's assume that on a slower machine i.e Github Actions this could
* take 1 second. You could simulate this in your tests with sleep(1);
* in order to force a failure for *demonstration* purposes.
*/
OrderExporterJob::dispatch();
/*
* When that one-second delay is introduced via a slower machine or
* manually via a sleep() call. Then the following assertion will fail.
*/
$this->assertTrue(Storage::disk('s3')->exists(
'exports/orders-' . Carbon::now()->format('Y-m-d H:i:s') . '.csv',
));
}
// AFTER
/** @test */
public function a_spreadsheet_is_exported_to_s3()
{
/*
* Carbon::setTestNow() forces the timestamp to be the same for the
* duration of the test.
*/
Carbon::setTestNow(Carbon::now());
Order::factory()->count(3)->create();
OrderExporterJob::dispatch();
sleep(1);
/*
* Even with the hardcoded sleep() call present, the test will pass.
* NOTE: The sleep() is here for demonstration purposes only.
*/
$this->assertTrue(Storage::disk('s3')->exists(
'exports/orders-' . Carbon::now()->format('Y-m-d H:i:s') . '.csv',
));
}
Example 2: A temporary signed URL is being used in a test assertion
// BEFORE
/** @test */
public function guests_can_edit_their_information()
{
$guest = Guest::factory()->create();
$response = $this->get(
URL::temporarySignedRoute(
'guests.edit',
Carbon::now()->addDays(7), [
'contact' => $guest->uuid,
]
)
);
/*
* Similarly as per the previous example, we want to make an assertion on
* a time-sensitive value. Here, if the time were to change by a second
* then our assertion on the guestUpdateRoute then we would have an invalid
* signature. Using Carbon::setTestNow(Carbon::today()); will ensure the
* tests pass even if on a slow machine.
*/
$response->assertOk()
->assertPropValue('guest', $guest)
->assertPropValue('guestUpdateRoute', URL::temporarySignedRoute(
'guests.update', Carbon::now()->addDays(7), [
'guest' => $guest->uuid,
]
));
}
// AFTER
/** @test */
public function guests_can_edit_their_information()
{
/*
* Using Carbon::setTestNow(Carbon::today()); will ensure the tests
* pass even if on a slow machine.
*/
Carbon::setTestNow(Carbon::today());
$guest = Guest::factory()->create();
$response = $this->get(
URL::temporarySignedRoute(
'guests.edit',
Carbon::now()->addDays(7), [
'contact' => $guest->uuid,
]
)
);
sleep(1);
/*
* Even with the hardcoded sleep() call present, the test will pass.
* NOTE: The sleep() is here for demonstration purposes only.
*/
$response->assertOk()
->assertPropValue('guest', $guest)
->assertPropValue('guestUpdateRoute', URL::temporarySignedRoute(
'guests.update', Carbon::now()->addDays(7), [
'guest' => $guest->uuid,
]
));
}
Have questions or want to stay up to date? Find me on Twitter Get notified of new posts