Search code examples
phplaraveltestinglaravel-artisanlaravel-queue

Laravel Queue::fake() should be at the top of our tests?


It's a very simple question, I had this test:

public function test_expired_cases_empty_dont_dispatch_update_expired_class()
{
    $this->expired1->created_at = Carbon::now();
    $this->expired1->save();
    $this->expired2->created_at = Carbon::now()->subDays(30);
    $this->expired2->save();

    Queue::fake();

    $this->artisan('expiration:update-expired')->assertSuccessful();

    Queue::assertNotPushed(UpdateExpired::class);
}

So basically, expiration:update-expired command just check if I have expired cases, the criteria are 'created_at', '<', Carbon::now()->subDays(30), so the cases from the test should not be treated as expired, basically the test fails randomly and return The unexpected [App\Jobs\UpdateExpired] job was pushed, I think that the problem is a little delay of 1 or 2 seconds when Queue::fake(); is called, so when the command is called the expired2 case is actually expired.

So, my question is, this method should be at the top of the tests to ensure testing doesn't return this kind of random fails?


Solution

  • Your issue is a timing issue, not Queue::fake();, so it does not matter if you put it as the first line or before you call the command, you have to freeze time.

    Depending on what Laravel version you have (9+), you can freeze time so it does not continue advancing while you run a test, exactly what you need.

    So, your test should be like this:

    public function test_expired_cases_empty_dont_dispatch_update_expired_class()
    {
        $this->travelTo(now()->addSecond());
    
        $this->expired1->created_at = now();
        $this->expired1->save();
    
        $this->expired2->created_at = now()->subDays(30);
        $this->expired2->save();
    
        Queue::fake();
    
        $this->artisan('expiration:update-expired')->assertSuccessful();
    
        Queue::assertNotPushed(UpdateExpired::class);
    }
    

    See that you can use different variations of $this->travelTo(now()->addSecond()); (check the documentation link I have attached, and use the one that solves your issue). I have also replaced Carbon::now() with now().

    Let me know if this does not work for you, but it should be your solution.

    Remember, Queue::fake(); has nothing to do with timing, if you run it first or before the command, nothing changes in this case.