skip to Main Content

I’m using model factory methods in my Laravel test suite that return date ranges. For instance:

class WorkSiteFactory extends Factory
{
  // ... Other factory stuff....

  /**
  * Indicate that the Permit status is fully approved.
  */
  public function withApprovedPermit(): self
  {
      return $this->state([
        'permit_paid_at' => $this->faker->dateTimeBetween('-12 days', '-8 days'),
        'permit_approved_at' => $this->faker->dateTimeBetween('-12 days', '-8 days')
      ]);
  }
}

In tests, I’ll sometimes mock the date using Carbon::setTestNow() and then make my test assertions. But unfortunately the dateTimeBetween usages don’t respond to the mocked date.

use AppModelsWorkSite;
use IlluminateSupportCarbon;
use TestsTestCase;

class SchedulerControllerTest extends TestCase
{

    /** @test */
    public function index_returns_correct_number_of_work_sites(): void
    {  
        // Set the mocked "now" time
        $mockNow = Carbon::parse('2024-10-01');
        Carbon::setTestNow($mockNow);
    

        // Incomplete WorkSites won't show up as available
        WorkSite::factory()
            ->count(2)
            ->incomplete()
            ->create();
         
        // 4 WorkSites Ready to Work
        $workSites = WorkSite::factory()
            ->count(4)
            ->readyToWork() 
            ->withApprovedPermit() // ❌ <- this should set the permit dates correctly but it doesn't adjust according to the mocked date
            ->create();

        $response = $this
            ->actingAs(User::factory()->create())
            ->get(route('scheduler.index'))
            ->assertOk();

        // Then asserting that the right number of
        // WorkSites are returned according to the date...
        // ...
    }
}

I’ve done some source diving and thought maybe there was a way to override "now" in the Faker package but I don’t see any.

I’ve also tried to wrap the returned values in Carbon::instance() and Carbon::parse(), both didn’t work.

I’m sure there’s a hacky way of writing a method that takes the result of the Faker and adjusts it according to the Carbon::now() time but that seems like the wrong solution.

Thanks in advance!

2

Answers


  1. Faker is using PHP’s DateTime class and doesn’t use Carbon (which extends DateTime) in any way.

    As the model factory is only used in the test suites, you can add a parameter to withApprovedPermit() where you can provide a DateTime object.

    Or you can set the Date of the objects manually after creation (provided you have the required setters)

    Login or Signup to reply.
  2. Based on Carbon’s documentation, you can use some ways:

    • Carbon::parse('-12 days')->daysUntil('-8 days');
    • Carbon::parse('-12 days')->diff('-8 days')->stepBy('day');
    • CarbonPeriod::between('-12 days', '-8 days');

    This will give you an iterable from initial date to end date, what you can do is then use faker to get a random day from that iterable.

    If you do this, you will always use Carbon, so you can use Carbon::setTestNow(now()) anywhere and then the factory will take that into account:

    // Code before you trigger factory
    $mockNow = Carbon::parse('2024-10-01');
    Carbon::setTestNow($mockNow);
    
    // Part of the factory
    
    // Instead of
    $this->faker->dateTimeBetween('-12 days', '-8 days');
    
    // Use
    $this->faker->randomElement(CarbonCarbonPeriod::between('-12 days', '-8 days'));
    

    I saw you are using Carbon::setTestNow(), prefer to use "native" Laravel way of doing it:

    // Instead of
    $mockNow = Carbon::parse('2024-10-01');
    Carbon::setTestNow($mockNow);
    
    // Use
    $this->travelTo(Carbon::parse('2024-10-01'));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search